diff --git a/authorizer-app-backend/authorizer.yml b/authorizer-app-backend/authorizer.yml index 22dde7f7..5c6116dd 100644 --- a/authorizer-app-backend/authorizer.yml +++ b/authorizer-app-backend/authorizer.yml @@ -23,6 +23,7 @@ restSourceClients: - sourceType: FitBit authorizationEndpoint: https://www.fitbit.com/oauth2/authorize tokenEndpoint: https://api.fitbit.com/oauth2/token + deregistrationEndpoint: https://api.fitbit.com/oauth2/revoke clientId: clientSecret: scope: activity heartrate sleep profile diff --git a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/api/ApiDeclarations.kt b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/api/ApiDeclarations.kt index b8c58348..03001042 100644 --- a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/api/ApiDeclarations.kt +++ b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/api/ApiDeclarations.kt @@ -32,6 +32,8 @@ data class RestOauth2AccessToken( val tokenType: String? = null, @SerialName("user_id") val externalUserId: String? = null, + @SerialName("scope") + val scope: String? = null, ) @Serializable @@ -101,6 +103,7 @@ data class RequestTokenPayload( val oauth_token: String? = null, val oauth_verifier: String? = null, val oauth_token_secret: String? = null, + val scope: String? = null, ) data class ShareableClientDetail( diff --git a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/RestSourceUserRepositoryImpl.kt b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/RestSourceUserRepositoryImpl.kt index 022ef782..b72cb94a 100644 --- a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/RestSourceUserRepositoryImpl.kt +++ b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/RestSourceUserRepositoryImpl.kt @@ -78,6 +78,7 @@ class RestSourceUserRepositoryImpl( this.refreshToken = token.refreshToken this.expiresIn = token.expiresIn this.expiresAt = Instant.now().plusSeconds(token.expiresIn.toLong()) - expiryTimeMargin + this.scope = token.scope } else { this.externalUserId = null this.authorized = false diff --git a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/entity/RestSourceUser.kt b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/entity/RestSourceUser.kt index 2216532b..bc2edf9b 100644 --- a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/entity/RestSourceUser.kt +++ b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/doa/entity/RestSourceUser.kt @@ -108,6 +108,9 @@ class RestSourceUser( @Column(name = "times_reset") var timesReset: Long = 0, + @Column(name = "scope") + var scope: String? = null, + @OneToMany( fetch = FetchType.EAGER, targetEntity = RegistrationState::class, diff --git a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/service/OAuth2RestSourceAuthorizationService.kt b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/service/OAuth2RestSourceAuthorizationService.kt index 77f4e5f1..99e7f877 100644 --- a/authorizer-app-backend/src/main/java/org/radarbase/authorizer/service/OAuth2RestSourceAuthorizationService.kt +++ b/authorizer-app-backend/src/main/java/org/radarbase/authorizer/service/OAuth2RestSourceAuthorizationService.kt @@ -81,16 +81,47 @@ open class OAuth2RestSourceAuthorizationService( } } - override suspend fun revokeToken(user: RestSourceUser): Boolean = withContext(Dispatchers.IO) { + override suspend fun revokeToken(user: RestSourceUser): Boolean { val accessToken = user.accessToken ?: run { logger.error("Cannot revoke token of user {} without an access token", user.userId) - return@withContext false + return false } - logger.info("Requesting to revoke access token") - val response = submitForm(user.sourceType) { - append("token", accessToken) + // revoke token using the deregistrationEndpoint token endpoint + val authConfig = clients.forSourceType(user.sourceType) + val deregistrationEndpoint = checkNotNull(authConfig.deregistrationEndpoint) { "Missing deregistration endpoint configuration" } + + val isSuccess = try { + withContext(Dispatchers.IO) { + val response = httpClient.submitForm { + url { + takeFrom(deregistrationEndpoint) + parameters.append("token", accessToken) + parameters.append("client_id", authConfig.clientId!!) + } + } + if (response.status.isSuccess()) { + true + } else { + logger.error( + "Failed to revoke token for user {}: {}", + user.userId, + response.bodyAsText().take(512), + ) + false + } + } + } catch (ex: Exception) { + logger.warn("Revoke endpoint error: {}", ex.toString()) + false + } + + return if (isSuccess) { + logger.info("Successfully revoked token for user {}", user.userId) + true + } else { + logger.error("Failed to revoke token for user {}", user.userId) + false } - response.status.isSuccess() } override suspend fun revokeToken(externalId: String, sourceType: String, token: String): Boolean = diff --git a/authorizer-app-backend/src/main/resources/db/changelog/changes/00000000000004_add_scope_column.xml b/authorizer-app-backend/src/main/resources/db/changelog/changes/00000000000004_add_scope_column.xml new file mode 100644 index 00000000..5d6222b3 --- /dev/null +++ b/authorizer-app-backend/src/main/resources/db/changelog/changes/00000000000004_add_scope_column.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/docker/etc/rest-source-authorizer/authorizer.yml b/docker/etc/rest-source-authorizer/authorizer.yml index 0b4eff17..332da859 100644 --- a/docker/etc/rest-source-authorizer/authorizer.yml +++ b/docker/etc/rest-source-authorizer/authorizer.yml @@ -26,6 +26,7 @@ restSourceClients: - sourceType: FitBit authorizationEndpoint: https://www.fitbit.com/oauth2/authorize tokenEndpoint: https://api.fitbit.com/oauth2/token + deregistrationEndpoint: https://api.fitbit.com/oauth2/revoke clientId: Fitbit-clientid clientSecret: Fitbit-clientsecret scope: activity heartrate sleep profile diff --git a/docker/etc/rest-source-authorizer/authorizer.yml.template b/docker/etc/rest-source-authorizer/authorizer.yml.template index f2fcf95c..29129d35 100644 --- a/docker/etc/rest-source-authorizer/authorizer.yml.template +++ b/docker/etc/rest-source-authorizer/authorizer.yml.template @@ -30,6 +30,7 @@ restSourceClients: - sourceType: FitBit authorizationEndpoint: https://www.fitbit.com/oauth2/authorize tokenEndpoint: https://api.fitbit.com/oauth2/token + deregistrationEndpoint: https://api.fitbit.com/oauth2/revoke clientId: Fitbit-clientid clientSecret: Fitbit-clientsecret scope: activity heartrate sleep profile