diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 8a85075..1352e12 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -9,15 +9,15 @@ plugins { } group = "org.babyfish.graphql.provider" -version = "0.0.7" +version = "0.0.8" repositories { mavenCentral() } dependencies { - implementation("org.babyfish.graphql.provider:graphql-provider-starter-dgs:0.0.7") - ksp("org.babyfish.kimmer:kimmer-ksp:0.3.1") + implementation("org.babyfish.graphql.provider:graphql-provider-starter-dgs:0.0.8") + ksp("org.babyfish.kimmer:kimmer-ksp:0.3.3") runtimeOnly("io.r2dbc:r2dbc-h2:0.8.5.RELEASE") } diff --git a/project/.DS_Store b/project/.DS_Store index 7026dca..be9a90b 100644 Binary files a/project/.DS_Store and b/project/.DS_Store differ diff --git a/project/build.gradle.kts b/project/build.gradle.kts index c579d85..5f95a88 100644 --- a/project/build.gradle.kts +++ b/project/build.gradle.kts @@ -1,4 +1,4 @@ allprojects { group = "org.babyfish.graphql.provider" - version = "0.0.7" + version = "0.0.8" } \ No newline at end of file diff --git a/project/graphql-provider-starter-dgs/src/main/kotlin/org/babyfish/graphql/provider/starter/dgs/DynamicCodeRegistry.kt b/project/graphql-provider-starter-dgs/src/main/kotlin/org/babyfish/graphql/provider/starter/dgs/DynamicCodeRegistry.kt index 525356e..5b9a6b2 100644 --- a/project/graphql-provider-starter-dgs/src/main/kotlin/org/babyfish/graphql/provider/starter/dgs/DynamicCodeRegistry.kt +++ b/project/graphql-provider-starter-dgs/src/main/kotlin/org/babyfish/graphql/provider/starter/dgs/DynamicCodeRegistry.kt @@ -8,6 +8,7 @@ import org.babyfish.graphql.provider.meta.MetaProvider import org.babyfish.graphql.provider.runtime.DataFetchers import org.babyfish.graphql.provider.runtime.cfg.GraphQLProviderProperties import org.babyfish.graphql.provider.runtime.registryDynamicCodeRegistry +import org.babyfish.graphql.provider.security.AuthenticationExtractor import org.babyfish.graphql.provider.security.jwt.JwtAuthenticationService @DgsComponent @@ -15,7 +16,8 @@ internal open class DynamicCodeRegistry( private val properties: GraphQLProviderProperties, private val dataFetchers: DataFetchers, private val metaProvider: MetaProvider, - private val jwtAuthenticationService: JwtAuthenticationService? + private val jwtAuthenticationService: JwtAuthenticationService?, + private val authenticationExtractor: AuthenticationExtractor? ) { @DgsCodeRegistry open fun registry( @@ -27,7 +29,8 @@ internal open class DynamicCodeRegistry( properties, dataFetchers, metaProvider, - jwtAuthenticationService + jwtAuthenticationService, + authenticationExtractor ) } } \ No newline at end of file diff --git a/project/graphql-provider/build.gradle.kts b/project/graphql-provider/build.gradle.kts index cf931a5..4301345 100644 --- a/project/graphql-provider/build.gradle.kts +++ b/project/graphql-provider/build.gradle.kts @@ -40,9 +40,6 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-data-r2dbc:2.6.6") testImplementation("org.springframework.boot:spring-boot-starter-webflux:2.6.6") testRuntimeOnly("io.r2dbc:r2dbc-h2:0.8.5.RELEASE") - - implementation("org.springdoc:springdoc-openapi-kotlin:1.6.7") - implementation("org.springdoc:springdoc-openapi-webflux-ui:1.6.7") } ksp { diff --git a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/AuthenticationSDLDefinitions.kt b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/AuthenticationSDLDefinitions.kt index 516b755..9c5c629 100644 --- a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/AuthenticationSDLDefinitions.kt +++ b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/AuthenticationSDLDefinitions.kt @@ -32,19 +32,19 @@ internal fun createAuthenticationSDLDefinitions( inputValueDefinition(InputValueDefinition("password", TypeName("String"))) }.build() } - api.updatePassword.trim().takeIf { it.isNotEmpty() }?.let { - mutationFields += FieldDefinition.newFieldDefinition().apply { + api.refreshAccessToken.trim().takeIf { it.isNotEmpty() }?.let { + queryFields += FieldDefinition.newFieldDefinition().apply { name(it) type(TypeName(AUTHENTICATION_RESULT)) - inputValueDefinition(InputValueDefinition("oldPassword", TypeName("String"))) - inputValueDefinition(InputValueDefinition("newPassword", TypeName("String"))) + inputValueDefinition(InputValueDefinition("refreshToken", TypeName("String"))) }.build() } - api.refreshAccessToken.trim().takeIf { it.isNotEmpty() }?.let { + api.updatePassword.trim().takeIf { it.isNotEmpty() }?.let { mutationFields += FieldDefinition.newFieldDefinition().apply { name(it) type(TypeName(AUTHENTICATION_RESULT)) - inputValueDefinition(InputValueDefinition("refreshToken", TypeName("String"))) + inputValueDefinition(InputValueDefinition("oldPassword", TypeName("String"))) + inputValueDefinition(InputValueDefinition("newPassword", TypeName("String"))) }.build() } } diff --git a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/RegistryDynamicCode.kt b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/RegistryDynamicCode.kt index 7a84d70..2250102 100644 --- a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/RegistryDynamicCode.kt +++ b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/runtime/RegistryDynamicCode.kt @@ -3,19 +3,23 @@ package org.babyfish.graphql.provider.runtime import graphql.schema.DataFetcher import graphql.schema.FieldCoordinates import graphql.schema.GraphQLCodeRegistry -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.reactor.mono import org.babyfish.graphql.provider.meta.MetaProvider import org.babyfish.graphql.provider.runtime.cfg.GraphQLProviderProperties +import org.babyfish.graphql.provider.security.AuthenticationExtractor import org.babyfish.graphql.provider.security.jwt.JwtAuthenticationService fun GraphQLCodeRegistry.Builder.registryDynamicCodeRegistry( properties: GraphQLProviderProperties, dataFetchers: DataFetchers, metaProvider: MetaProvider, - jwtAuthenticationService: JwtAuthenticationService? + jwtAuthenticationService: JwtAuthenticationService?, + authenticationExtractor: AuthenticationExtractor? ) { - registerAuthenticationApi(properties, jwtAuthenticationService) + registerAuthenticationApi( + properties, + jwtAuthenticationService, + authenticationExtractor + ) for (prop in metaProvider.queryType.props.values) { val coordinates = FieldCoordinates.coordinates("Query", prop.name) val dataFetcher = DataFetcher { @@ -49,14 +53,15 @@ fun GraphQLCodeRegistry.Builder.registryDynamicCodeRegistry( private fun GraphQLCodeRegistry.Builder.registerAuthenticationApi( properties: GraphQLProviderProperties, - jwtAuthenticationService: JwtAuthenticationService? + jwtAuthenticationService: JwtAuthenticationService?, + authenticationExtractor: AuthenticationExtractor? ) { val api = properties.security.api if (api.graphql && jwtAuthenticationService !== null) { - api.login.trim().takeIf { it.isNotEmpty() }?.let { - val coordinates = FieldCoordinates.coordinates("Query", it) - val dataFetcher = DataFetcher { - mono(Dispatchers.Unconfined) { + api.login.trim().takeIf { it.isNotEmpty() }?.let { fn -> + val coordinates = FieldCoordinates.coordinates("Query", fn) + val dataFetcher = DataFetcher { it + graphqlMono(ExecutorContext(null, it, authenticationExtractor?.get(it))) { val username = it.getArgument(api.usernameArgName) val password = it.getArgument("password") jwtAuthenticationService.login(username, password) @@ -64,23 +69,23 @@ private fun GraphQLCodeRegistry.Builder.registerAuthenticationApi( } dataFetcher(coordinates, dataFetcher) } - api.updatePassword.trim().takeIf { it.isNotEmpty() }?.let { - val coordinates = FieldCoordinates.coordinates("Mutation", it) + api.refreshAccessToken.trim().takeIf { it.isNotEmpty() }?.let { fn -> + val coordinates = FieldCoordinates.coordinates("Query", fn) val dataFetcher = DataFetcher { - mono(Dispatchers.Unconfined) { - val oldPassword = it.getArgument("oldPassword") - val newPassword = it.getArgument("newPassword") - jwtAuthenticationService.updatePassword(oldPassword, newPassword) + graphqlMono(ExecutorContext(null, it, authenticationExtractor?.get(it))) { + val refreshToken = it.getArgument("refreshToken") + jwtAuthenticationService.refreshAccessToken(refreshToken) }.toFuture() } dataFetcher(coordinates, dataFetcher) } - api.refreshAccessToken.trim().takeIf { it.isNotEmpty() }?.let { - val coordinates = FieldCoordinates.coordinates("Mutation", it) + api.updatePassword.trim().takeIf { it.isNotEmpty() }?.let { fn -> + val coordinates = FieldCoordinates.coordinates("Mutation", fn) val dataFetcher = DataFetcher { - mono(Dispatchers.Unconfined) { - val refreshToken = it.getArgument("refreshToken") - jwtAuthenticationService.refreshAccessToken(refreshToken) + graphqlMono(ExecutorContext(null, it, authenticationExtractor?.get(it))) { + val oldPassword = it.getArgument("oldPassword") + val newPassword = it.getArgument("newPassword") + jwtAuthenticationService.updatePassword(oldPassword, newPassword) }.toFuture() } dataFetcher(coordinates, dataFetcher) diff --git a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/JwtAuthenticationServiceImpl.kt b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/JwtAuthenticationServiceImpl.kt index 8f9e18c..fed9314 100644 --- a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/JwtAuthenticationServiceImpl.kt +++ b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/JwtAuthenticationServiceImpl.kt @@ -74,7 +74,7 @@ internal open class JwtAuthenticationServiceImpl( newPassword: String ): JwtAuthenticationResult { authenticationBehaviorProvider.validateRawPassword(newPassword) - val user = authenticationOrNull()?.details as UserDetails? + val user = authenticationOrNull()?.principal as? UserDetails ?: throw JwtUpdatePasswordException(JwtUpdatePasswordException.Reason.UNAUTHENTICATED) if (!matches(oldPassword, user.password)) { throw JwtUpdatePasswordException(JwtUpdatePasswordException.Reason.ILLEGAL_OLD_PASSWORD) diff --git a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/cfg/RestApiConfiguration.kt b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/cfg/RestApiConfiguration.kt index 8305c69..b3cbb74 100644 --- a/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/cfg/RestApiConfiguration.kt +++ b/project/graphql-provider/src/main/kotlin/org/babyfish/graphql/provider/security/jwt/cfg/RestApiConfiguration.kt @@ -1,23 +1,12 @@ package org.babyfish.graphql.provider.security.jwt.cfg -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import kotlinx.coroutines.reactor.awaitSingle -import org.babyfish.graphql.provider.runtime.ExecutorContext import org.babyfish.graphql.provider.runtime.cfg.GraphQLProviderProperties -import org.babyfish.graphql.provider.runtime.withExecutorContext import org.babyfish.graphql.provider.security.cfg.toGraphQLError -import org.babyfish.graphql.provider.security.jwt.JwtAuthenticationManager import org.babyfish.graphql.provider.security.jwt.JwtAuthenticationService -import org.babyfish.graphql.provider.security.jwt.JwtTokenConverter -import org.springdoc.core.GroupedOpenApi -import org.springdoc.core.annotations.RouterOperation -import org.springdoc.core.annotations.RouterOperations import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.reactive.function.server.ServerResponse import org.springframework.web.reactive.function.server.bodyValueAndAwait import org.springframework.web.reactive.function.server.coRouter @@ -26,25 +15,10 @@ import org.springframework.web.reactive.function.server.coRouter @ConditionalOnExpression(RestApiConfiguration.SPEL) internal open class RestApiConfiguration( private val properties: GraphQLProviderProperties, - private val jwtAuthenticationService: JwtAuthenticationService, - private val jwtTokenConverter: JwtTokenConverter, - private val jwtAuthenticationManager: JwtAuthenticationManager + private val jwtAuthenticationService: JwtAuthenticationService ) { @Bean - @RouterOperations( - RouterOperation( - path = "$DOC_BASE/login", - method = arrayOf(RequestMethod.GET), - parameterTypes = [String::class, String::class], - operation = Operation( - parameters = [ - Parameter(name = "username", required = true), - Parameter(name = "password", required = true) - ] - ) - ) - ) open fun authenticationRouter() = coRouter { val api = properties.security.api @@ -65,29 +39,8 @@ internal open class RestApiConfiguration( } } } - api.updatePassword.trim().takeIf { it.isNotEmpty() }?.let { fn-> - PUT("${api.restPath}/$fn") { - val jwtToken = jwtTokenConverter.convert(it.exchange()).awaitSingle() - val authentication = jwtAuthenticationManager.authenticate(jwtToken).awaitSingle() - withExecutorContext(ExecutorContext(null, null, authentication)) { - val oldPassword = it.queryParam("oldPassword").get() - val newPassword = it.queryParam("newPassword").get() - try { - ServerResponse.ok() - .bodyValueAndAwait( - jwtAuthenticationService.updatePassword(oldPassword, newPassword) - ) - } catch (ex: Throwable) { - ServerResponse.status(HttpStatus.BAD_GATEWAY) - .bodyValueAndAwait( - ex.toGraphQLError() - ) - } - } - } - } api.refreshAccessToken.trim().takeIf { it.isNotEmpty() }?.let { fn-> - PUT("${api.refreshAccessToken}/$fn") { + GET("${api.restPath}/$fn") { val refreshToken = it.queryParam("refreshToken").get() try { ServerResponse.ok() @@ -104,14 +57,6 @@ internal open class RestApiConfiguration( } } - @Bean - open fun storeOpenApi(): GroupedOpenApi? { - val path = properties.security.api.restPath - val paths = arrayOf("/${path}/**") - return GroupedOpenApi.builder().group(path).pathsToMatch(*paths) - .build() - } - companion object { private const val REST_PATH = "restPath"