From ceaacc9685a59f5725c88017e64ab8c5940532d9 Mon Sep 17 00:00:00 2001 From: Sambo Chea Date: Sun, 8 Aug 2021 18:55:52 +0700 Subject: [PATCH] Task: Completed login and get token from user and able to change user password by username and updated the security filters and add auth service and auth details. And more add login mutation in resolver --- cubetiq-security-jwt | 2 +- .../cubetiqs/graphql/demo/config/WebConfig.kt | 1 - .../graphql/demo/config/WebSecurityConfig.kt | 21 +++--- .../graphql/demo/repository/UserRepository.kt | 4 ++ .../mutation/LoginMutationResolver.kt | 22 +++++++ .../resolver/mutation/UserMutationResolver.kt | 12 +++- .../graphql/demo/security/AuthDetails.kt | 64 +++++++++++++++++++ .../graphql/demo/security/AuthService.kt | 32 ++++++++++ .../graphql/demo/secutiry/AuthService.kt | 12 ---- .../src/main/resources/schema/schema.graphqls | 3 + .../src/main/resources/schema/user.graphql | 9 +++ frontend/src/App.tsx | 22 +++---- 12 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/resolver/mutation/LoginMutationResolver.kt create mode 100644 dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthDetails.kt create mode 100644 dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/security/AuthService.kt delete mode 100644 dgs-graphql/src/main/kotlin/com/cubetiqs/graphql/demo/secutiry/AuthService.kt 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}`}

} );