diff --git a/cubetiq-security-jwt b/cubetiq-security-jwt index 9a114db..d69f52f 160000 --- a/cubetiq-security-jwt +++ b/cubetiq-security-jwt @@ -1 +1 @@ -Subproject commit 9a114dba23c52619955999f867bc1b95d3870bb3 +Subproject commit d69f52fee0d6c8c0fad3eab64dc0645470168a49 diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebConfig.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebConfig.kt index 7d22af0..f8954a9 100644 --- a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebConfig.kt +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebConfig.kt @@ -9,7 +9,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @EnableWebMvc class WebConfig : WebMvcConfigurer { override fun addCorsMappings(corsRegistry: CorsRegistry) { - println("Hello World") corsRegistry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebSecurityConfig.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebSecurityConfig.kt index 52075ba..9eda151 100644 --- a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebSecurityConfig.kt +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/config/WebSecurityConfig.kt @@ -1,6 +1,6 @@ package com.cubetiqs.graphql.demo.config -import com.cubetiqs.graphql.demo.secutiry.AuthService +import com.cubetiqs.graphql.demo.security.AuthService import com.cubetiqs.security.jwt.AuthenticationExceptionEntryPoint import com.cubetiqs.security.jwt.JwtSecurityConfigurer import org.springframework.beans.factory.annotation.Autowired @@ -13,26 +13,25 @@ import org.springframework.security.config.http.SessionCreationPolicy @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +@EnableGlobalMethodSecurity(prePostEnabled = true) class WebSecurityConfig : WebSecurityConfigurerAdapter() { @Autowired private lateinit var authService: AuthService override fun configure(http: HttpSecurity) { - http.csrf() - .and() - .httpBasic() - .disable() + http.csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() + + http .exceptionHandling() .authenticationEntryPoint(AuthenticationExceptionEntryPoint()) - .and() + + http .apply(JwtSecurityConfigurer(authService)) - .and() + + http .authorizeRequests() - .anyRequest() - .permitAll() + .anyRequest().permitAll() } } \ No newline at end of file diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/repository/UserRepository.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/repository/UserRepository.kt index a1800d4..3e11911 100644 --- a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/repository/UserRepository.kt +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/repository/UserRepository.kt @@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository +import java.util.* @Repository interface UserRepository : JpaRepository { @@ -14,4 +15,7 @@ interface UserRepository : JpaRepository { @Query("select (count(u) > 0) from User u where u.username = ?1") fun existsAllByUsername(username: String): Boolean + + @Query("select u from User u where u.username = ?1") + fun queryByUsername(username: String): Optional } \ No newline at end of file diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/LoginMutationResolver.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/LoginMutationResolver.kt new file mode 100644 index 0000000..b3411c3 --- /dev/null +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/LoginMutationResolver.kt @@ -0,0 +1,22 @@ +package com.cubetiqs.graphql.demo.resolver.mutation + +import com.cubetiqs.graphql.demo.context.GMutation +import com.cubetiqs.graphql.demo.dgmodel.DgsConstants +import com.cubetiqs.graphql.demo.dgmodel.types.LoginResponse +import com.cubetiqs.graphql.demo.security.AuthService +import com.cubetiqs.security.jwt.util.JwtUtils +import com.netflix.graphql.dgs.DgsMutation +import org.springframework.beans.factory.annotation.Autowired + +@GMutation +class LoginMutationResolver { + @Autowired + private lateinit var authService: AuthService + + @DgsMutation(field = DgsConstants.MUTATION.Login) + fun login(username: String, password: String): LoginResponse { + val auth = authService.login(username, password) + val token = JwtUtils.encryptToken(auth) + return LoginResponse(token) + } +} \ No newline at end of file diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/UserMutationResolver.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/UserMutationResolver.kt index 8ce7bb8..131bfb6 100644 --- a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/UserMutationResolver.kt +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/UserMutationResolver.kt @@ -2,11 +2,14 @@ package com.cubetiqs.graphql.demo.resolver.mutation import com.cubetiqs.graphql.demo.context.GMutation import com.cubetiqs.graphql.demo.dgmodel.DgsConstants +import com.cubetiqs.graphql.demo.dgmodel.types.UserChangePasswordInput import com.cubetiqs.graphql.demo.domain.user.User import com.cubetiqs.graphql.demo.domain.user.UserInput import com.cubetiqs.graphql.demo.domain.user.UserMapper import com.cubetiqs.graphql.demo.repository.UserRepository +import com.cubetiqs.security.jwt.util.JwtUtils import com.netflix.graphql.dgs.DgsMutation +import com.netflix.graphql.dgs.exceptions.DgsEntityNotFoundException import org.springframework.beans.factory.annotation.Autowired import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional @@ -18,9 +21,16 @@ class UserMutationResolver @Autowired constructor( @DgsMutation(field = DgsConstants.MUTATION.CreateUser) @Transactional(propagation = Propagation.REQUIRES_NEW) fun createUser(input: UserInput): User { - if (userRepository.existsAllByUsername(input.username ?: "")) throw Exception("Username has been already existed!") + if (userRepository.existsAllByUsername(input.username ?: "")) throw DgsEntityNotFoundException("Username has been already existed!") val user = UserMapper.fromInputToUser(input) return userRepository.save(user) } + + @DgsMutation(field = DgsConstants.MUTATION.ChangeUserPassword) + fun changePassword(input: UserChangePasswordInput): User { + val user = userRepository.queryByUsername(input.username).orElse(null) ?: throw DgsEntityNotFoundException("User not found!") + user.password = JwtUtils.passwordEncoder().encode(input.password) + return userRepository.save(user) + } } \ No newline at end of file diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthDetails.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthDetails.kt new file mode 100644 index 0000000..2f13bd2 --- /dev/null +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthDetails.kt @@ -0,0 +1,64 @@ +package com.cubetiqs.graphql.demo.security + +import com.cubetiqs.graphql.demo.domain.user.User +import com.cubetiqs.security.jwt.util.JwtUtils +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.UserDetails + +data class AuthDetails( + private var code: String? = null, + private var name: String? = null, + private var username: String? = null, + private var password: String? = null, + private var authorities: Collection? = null, + private var enabled: Boolean? = null, +) : UserDetails { + override fun getAuthorities(): MutableCollection { + return authorities?.map { SimpleGrantedAuthority(it) }?.toMutableList() ?: mutableListOf( + SimpleGrantedAuthority( + "USER" + ) + ) + } + + override fun getPassword(): String { + return password ?: "" + } + + override fun getUsername(): String { + return username ?: "" + } + + override fun isAccountNonExpired(): Boolean { + return true + } + + override fun isAccountNonLocked(): Boolean { + return true + } + + override fun isCredentialsNonExpired(): Boolean { + return true + } + + override fun isEnabled(): Boolean { + return enabled ?: false + } + + fun isPasswordValid(password: String): Boolean { + return JwtUtils.passwordEncoder().matches(password, this.getPassword()) + } + + companion object { + fun fromUser(user: User): AuthDetails { + return AuthDetails( + code = user.code, + name = user.name, + username = user.username, + password = user.password, + enabled = user.enabled, + ) + } + } +} \ No newline at end of file diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthService.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthService.kt new file mode 100644 index 0000000..6c829f8 --- /dev/null +++ b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthService.kt @@ -0,0 +1,32 @@ +package com.cubetiqs.graphql.demo.security + +import com.cubetiqs.graphql.demo.repository.UserRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.access.AccessDeniedException +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.stereotype.Service + +@Service +class AuthService : UserDetailsService { + @Autowired + private lateinit var userRepository: UserRepository + + private fun findAuthDetailsByUsername(username: String): AuthDetails { + val user = userRepository.queryByUsername(username).orElse(null) ?: throw Exception("User not found!") + return AuthDetails.fromUser(user) + } + + override fun loadUserByUsername(username: String?): UserDetails { + return findAuthDetailsByUsername(username ?: "") + } + + fun login(username: String, password: String): AuthDetails { + val auth = findAuthDetailsByUsername(username) + if (auth.isPasswordValid(password)) { + return auth + } else { + throw AccessDeniedException("Username and password is incorrect!") + } + } +} \ No newline at end of file diff --git a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/secutiry/AuthService.kt b/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/secutiry/AuthService.kt deleted file mode 100644 index 8a62126..0000000 --- a/dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/secutiry/AuthService.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.cubetiqs.graphql.demo.secutiry - -import org.springframework.security.core.userdetails.UserDetails -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.stereotype.Service - -@Service -class AuthService : UserDetailsService { - override fun loadUserByUsername(username: String?): UserDetails { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/dgs-graphql/src/main/resources/schema/schema.graphqls b/dgs-graphql/src/main/resources/schema/schema.graphqls index bdcc2a7..e475a17 100644 --- a/dgs-graphql/src/main/resources/schema/schema.graphqls +++ b/dgs-graphql/src/main/resources/schema/schema.graphqls @@ -15,7 +15,10 @@ type Subscription { } type Mutation { + login(username: String!, password: String!): LoginResponse + createUser(input: UserInput): User! + changeUserPassword(input: UserChangePasswordInput): User! openAccount(input: AccountInput): Account! } \ No newline at end of file diff --git a/dgs-graphql/src/main/resources/schema/user.graphql b/dgs-graphql/src/main/resources/schema/user.graphql index 0bcf451..f565bae 100644 --- a/dgs-graphql/src/main/resources/schema/user.graphql +++ b/dgs-graphql/src/main/resources/schema/user.graphql @@ -11,4 +11,13 @@ input UserInput { password: String name: String enabled: Boolean +} + +type LoginResponse { + token: String +} + +input UserChangePasswordInput { + username: String! + password: String! } \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4a27ad9..0c2d9ff 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -40,23 +40,23 @@ const HELLO = gql` ` function App() { - // const {error, loading, data} = useQuery(ACCOUNTS) - const {error, loading, data} = useSubscription(HELLO) + const {error, loading, data} = useQuery(ACCOUNTS) + // const {error, loading, data} = useSubscription(HELLO) console.log(data) return ( <>

Accounts

{ loading || !data ?

Loading...

: - // data.fetchAccounts.map(account => ( - // <> - //
Account ID: {account.id}
- //
Account Code: {account.code}
- //
Account User: {account.user.name}
- // - // ) - // ) -

{`${data.hello}`}

+ data.fetchAccounts.map(account => ( + <> +
Account ID: {account.id}
+
Account Code: {account.code}
+
Account User: {account.user.name}
+ + ) + ) + //

{`${data.hello}`}

} );