Skip to content

Commit

Permalink
feat(auth): Add "offline_access" scope for long-lived refresh tokens
Browse files Browse the repository at this point in the history
Add the "offline_access" scope when requesting authentication and
refresh tokens. This ensures that the authentication provider issues
long-lived refresh tokens, allowing the CLI to obtain new
access tokens without requiring the user to log in frequently.

This allows for having long-lived refresh tokens for the
CLI, while still having the refresh tokens for the web application
expire after a shorter period of time in case there is no user
activity.

Signed-off-by: Wolfgang Klenk <wolfgang.klenk2@bosch.com>
  • Loading branch information
wkl3nk committed Feb 4, 2025
1 parent 2e89f71 commit df81e58
Show file tree
Hide file tree
Showing 4 changed files with 11 additions and 5 deletions.
10 changes: 8 additions & 2 deletions api/v1/client/src/commonMain/kotlin/auth/AuthService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@ class AuthService(
/**
* Generate a token for the given [username] and [password].
*/
suspend fun generateToken(username: String, password: String): TokenInfo =
suspend fun generateToken(username: String, password: String, scopes: Set<String> = emptySet()): TokenInfo =
client.submitForm(
url = tokenUrl,
formParameters = Parameters.build {
append("client_id", clientId)
append("username", username)
append("password", password)
append("grant_type", "password")
if (scopes.isNotEmpty()) {
append("scope", scopes.joinToString(" "))
}
}
).let { response ->
if (!response.status.isSuccess()) {
Expand All @@ -59,13 +62,16 @@ class AuthService(
/**
* Refresh the token for the given [refreshToken].
*/
suspend fun refreshToken(refreshToken: String): TokenInfo =
suspend fun refreshToken(refreshToken: String, scopes: Set<String> = emptySet()): TokenInfo =
client.submitForm(
url = tokenUrl,
formParameters = Parameters.build {
append("client_id", clientId)
append("refresh_token", refreshToken)
append("grant_type", "refresh_token")
if (scopes.isNotEmpty()) {
append("scope", scopes.joinToString(" "))
}
}
).let { response ->
if (!response.status.isSuccess()) {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/kotlin/LoginCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class LoginCommand : SuspendingCliktCommand(name = "login") {
clientId = clientId
)

val tokenInfo = authService.generateToken(username, password)
val tokenInfo = authService.generateToken(username, password, setOf("offline_access"))

AuthenticationStorage.store(
HostAuthenticationDetails(
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/kotlin/utils/AuthenticationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private fun createOrtServerClient(authDetails: HostAuthenticationDetails): OrtSe
clientId = authDetails.clientId
)

auth.refreshToken(authDetails.tokens.refresh).also {
auth.refreshToken(authDetails.tokens.refresh, setOf("offline_access")).also {
val updatedAuthDetails = authDetails.copy(
tokens = Tokens(it.accessToken, it.refreshToken)
)
Expand Down
2 changes: 1 addition & 1 deletion cli/src/test/kotlin/AuthLoginCommandTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class AuthLoginCommandTest : StringSpec({
"Auth login command" should {
"store the authentication information in a local file" {
mockkConstructor(AuthService::class)
coEvery { anyConstructed<AuthService>().generateToken("testUser", "testPassword") } returns TokenInfo(
coEvery { anyConstructed<AuthService>().generateToken("testUser", "testPassword", setOf("offline_access")) } returns TokenInfo(
accessToken = "testAccessToken",
refreshToken = "testRefreshToken",
expiresInSeconds = 3600
Expand Down

0 comments on commit df81e58

Please sign in to comment.