FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

JWT token refresh by leandroBorgesFerreira · Pull Request #760 · Writeopia/Writeopia · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .kt  (31) .sq  (2) .sql  (1) All 3 file types selected
Deleted files Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
          • TokenDao.kt
          • TokenDaoDelegator.kt
          • TokenEntity.kt
        • TokenEntity.sq
        • TokenCommonDao.kt
          • AuthRouting.kt
          • EmailRouting.kt
          • InstallAuth.kt
          • JwtConfig.kt
      • FixedTokenHandler.kt
      • AuthResponse.kt
      • TokenModels.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
package io.writeopia.auth.core.repository

import io.writeopia.auth.core.manager.AuthRepository
import io.writeopia.auth.core.manager.TokenData
import io.writeopia.common.utils.persistence.daos.TokenCommonDao
import io.writeopia.common.utils.persistence.daos.UserCommonDao
import io.writeopia.common.utils.persistence.daos.WorkspaceCommonDao
import io.writeopia.sdk.models.workspace.Workspace
import io.writeopia.sdk.models.user.WriteopiaUser
import io.writeopia.sdk.models.utils.ResultData
import kotlin.time.Clock
import kotlin.time.ExperimentalTime

class RoomAuthRepository(
Expand Down Expand Up @@ -36,12 +38,44 @@ class RoomAuthRepository(
userDao.insertUser(user, selected)
}

override suspend fun saveToken(userId: String, token: String) {
tokenCommonDao.saveToken(token = token, userId = userId)
override suspend fun saveTokens(
userId: String,
accessToken: String,
refreshToken: String?,
expiresAt: Long?
) {
tokenCommonDao.saveTokens(
userId = userId,
accessToken = accessToken,
refreshToken = refreshToken,
expiresAt = expiresAt
)
}

override suspend fun getAuthToken(): String? = tokenCommonDao.getTokenByUserId(getUser().id)

override suspend fun getRefreshToken(): String? =
tokenCommonDao.getTokenDetails(getUser().id)?.refreshToken

override suspend fun getTokenData(): TokenData? =
tokenCommonDao.getTokenDetails(getUser().id)?.let { details ->
TokenData(
accessToken = details.accessToken,
refreshToken = details.refreshToken,
accessTokenExpiresAt = details.accessTokenExpiresAt
)
}

override suspend fun isAccessTokenExpired(): Boolean {
val tokenData = getTokenData() ?: return true
val expiresAt = tokenData.accessTokenExpiresAt ?: return false
return Clock.System.now().toEpochMilliseconds() >= expiresAt
}

override suspend fun clearTokens() {
tokenCommonDao.deleteToken(getUser().id)
}

override suspend fun useOffline() {
unselectAllUsers()
saveUser(WriteopiaUser.disconnectedUser().copy(id = WriteopiaUser.DISCONNECTED), true)
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.writeopia.auth.core.token

import io.writeopia.sdk.network.oauth.BearerTokenHandler
import io.writeopia.sdk.network.oauth.TokenRefreshResult

actual object AppBearerTokenHandler : BearerTokenHandler {
actual override suspend fun getIdToken(): String? = null

actual override suspend fun getRefreshToken(): String? = null

actual override suspend fun refreshTokens(): TokenRefreshResult = TokenRefreshResult.NoRefreshToken
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import io.writeopia.sdk.serialization.data.auth.ManageUserRequest
import io.writeopia.sdk.serialization.data.auth.PasswordResetWithCodeRequest
import io.writeopia.sdk.serialization.data.auth.PasswordVerifyCodeRequest
import io.writeopia.sdk.serialization.data.auth.RegisterRequest
import io.writeopia.sdk.serialization.data.auth.RefreshTokenRequest
import io.writeopia.sdk.serialization.data.auth.RegisterResponse
import io.writeopia.sdk.serialization.data.auth.TokenRefreshResponse
import io.writeopia.sdk.serialization.data.WriteopiaUserApi
import io.writeopia.sdk.serialization.data.auth.ResetPasswordRequest

Expand Down Expand Up @@ -216,4 +218,36 @@ class AuthApi(private val client: HttpClient, private val baseUrl: String) {
println("getCurrentUser error: ${e.message}")
ResultData.Error(e)
}

suspend fun refreshToken(refreshToken: String): ResultData<TokenRefreshResponse> = try {
val response = client.post("$baseUrl/api/auth/refresh") {
contentType(ContentType.Application.Json)
setBody(RefreshTokenRequest(refreshToken))
}

if (response.status.isSuccess()) {
ResultData.Complete(response.body<TokenRefreshResponse>())
} else {
ResultData.Error()
}
} catch (e: Exception) {
println("refreshToken error: ${e.message}")
ResultData.Error(e)
}

suspend fun logout(refreshToken: String): ResultData<Unit> = try {
val response = client.post("$baseUrl/api/auth/logout") {
contentType(ContentType.Application.Json)
setBody(RefreshTokenRequest(refreshToken))
}

if (response.status.isSuccess()) {
ResultData.Complete(Unit)
} else {
ResultData.Error()
}
} catch (e: Exception) {
println("logout error: ${e.message}")
ResultData.Error(e)
}
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import io.writeopia.sdk.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

data class TokenData(
val accessToken: String,
val refreshToken: String?,
val accessTokenExpiresAt: Long?
)

interface AuthRepository : UserRepository {

override fun listenForUser(): Flow<WriteopiaUser> = flow { emit(getUser()) }
Expand All @@ -23,10 +29,25 @@ interface AuthRepository : UserRepository {

suspend fun saveUser(user: WriteopiaUser, selected: Boolean)

suspend fun saveToken(userId: String, token: String)
suspend fun saveTokens(
userId: String,
accessToken: String,
refreshToken: String?,
expiresAt: Long?
)

suspend fun getAuthToken(): String?

suspend fun getAccessToken(): String? = getAuthToken()

suspend fun getRefreshToken(): String?

suspend fun getTokenData(): TokenData?

suspend fun isAccessTokenExpired(): Boolean

suspend fun clearTokens()

suspend fun useOffline()

override suspend fun getWorkspace(): Workspace?
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.writeopia.sdk.models.workspace.Workspace
import io.writeopia.sdk.models.user.WriteopiaUser
import io.writeopia.sdk.models.utils.ResultData
import io.writeopia.sql.WriteopiaDb
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

Expand Down Expand Up @@ -64,8 +65,41 @@ internal class SqlDelightAuthRepository(
?.selectTokenByUserId(getUser().id)
?.awaitAsOneOrNull()

override suspend fun saveToken(userId: String, token: String) {
writeopiaDb?.tokenEntityQueries?.insertToken(userId, token)
override suspend fun saveTokens(
userId: String,
accessToken: String,
refreshToken: String?,
expiresAt: Long?
) {
writeopiaDb?.tokenEntityQueries?.insertToken(userId, accessToken, refreshToken, expiresAt)
}

override suspend fun getRefreshToken(): String? =
writeopiaDb?.tokenEntityQueries
?.selectTokensDetailsByUserId(getUser().id)
?.awaitAsOneOrNull()
?.refresh_token

override suspend fun getTokenData(): TokenData? =
writeopiaDb?.tokenEntityQueries
?.selectTokensDetailsByUserId(getUser().id)
?.awaitAsOneOrNull()
?.let { entity ->
TokenData(
accessToken = entity.access_token,
refreshToken = entity.refresh_token,
accessTokenExpiresAt = entity.access_token_expires_at
)
}

override suspend fun isAccessTokenExpired(): Boolean {
val tokenData = getTokenData() ?: return true
val expiresAt = tokenData.accessTokenExpiresAt ?: return false
return Clock.System.now().toEpochMilliseconds() >= expiresAt
}

override suspend fun clearTokens() {
writeopiaDb?.tokenEntityQueries?.deleteToken(getUser().id)
}

override suspend fun useOffline() {
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.writeopia.auth.core.token

import io.writeopia.sdk.network.oauth.BearerTokenHandler
import io.writeopia.sdk.network.oauth.TokenRefreshResult

expect object AppBearerTokenHandler : BearerTokenHandler {
override suspend fun getIdToken(): String?

override suspend fun getRefreshToken(): String?

override suspend fun refreshTokens(): TokenRefreshResult
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@file:OptIn(ExperimentalTime::class)

package io.writeopia.auth.core.token

import io.writeopia.auth.core.data.AuthApi
import io.writeopia.auth.core.manager.AuthRepository
import io.writeopia.sdk.models.utils.ResultData
import io.writeopia.sdk.network.oauth.BearerTokenHandler
import io.writeopia.sdk.network.oauth.TokenRefreshResult
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.time.Clock
import kotlin.time.ExperimentalTime

class TokenManager(
private val authRepository: AuthRepository,
private val authApi: AuthApi
) : BearerTokenHandler {

private val refreshMutex = Mutex()

override suspend fun getIdToken(): String? = authRepository.getAccessToken()

override suspend fun getRefreshToken(): String? = authRepository.getRefreshToken()

override suspend fun refreshTokens(): TokenRefreshResult {
return refreshMutex.withLock {
val refreshToken = authRepository.getRefreshToken()
?: return@withLock TokenRefreshResult.NoRefreshToken

when (val result = authApi.refreshToken(refreshToken)) {
is ResultData.Complete -> {
val tokenResponse = result.data
val userId = authRepository.getUser().id

// Calculate expiry time (15 minutes from now, with 1-minute buffer)
val expiresAt = Clock.System.now().toEpochMilliseconds() + (14 * 60 * 1000L)

authRepository.saveTokens(
userId = userId,
accessToken = tokenResponse.accessToken,
refreshToken = tokenResponse.refreshToken,
expiresAt = expiresAt
)

TokenRefreshResult.Success(
accessToken = tokenResponse.accessToken,
refreshToken = tokenResponse.refreshToken
)
}
is ResultData.Error -> TokenRefreshResult.Failure
else -> TokenRefreshResult.Failure
}
}
}
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.writeopia.sdk.models.user.WriteopiaUser
import io.writeopia.sdk.models.utils.ResultData
import io.writeopia.sdk.models.workspace.Workspace
import kotlinx.browser.localStorage
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

Expand Down Expand Up @@ -34,7 +35,7 @@ internal class LocalStorageAuthRepository : AuthRepository {
getAuthToken().takeIf { it?.isNotEmpty() == true } != null

override suspend fun logout(): ResultData<Boolean> {
localStorage.removeItem(KEY_TOKEN)
clearTokens()
localStorage.removeItem(KEY_USER_ID)
localStorage.removeItem(KEY_USER_EMAIL)
localStorage.removeItem(KEY_USER_NAME)
Expand All @@ -44,6 +45,7 @@ internal class LocalStorageAuthRepository : AuthRepository {
localStorage.removeItem(KEY_WORKSPACE_USER_ID)
localStorage.removeItem(KEY_WORKSPACE_NAME)
localStorage.removeItem(KEY_WORKSPACE_LAST_SYNC)
localStorage.removeItem(KEY_WORKSPACE_LAST_EVENT_SYNC)
localStorage.removeItem(KEY_WORKSPACE_SELECTED)
localStorage.removeItem(KEY_WORKSPACE_ROLE)
clearForgotPasswordData()
Expand All @@ -61,11 +63,7 @@ internal class LocalStorageAuthRepository : AuthRepository {
}

override suspend fun getAuthToken(): String? =
localStorage.getItem(KEY_TOKEN)

override suspend fun saveToken(userId: String, token: String) {
localStorage.setItem(KEY_TOKEN, token)
}
localStorage.getItem(KEY_ACCESS_TOKEN)

override suspend fun useOffline() {
val user = getUser()
Expand Down Expand Up @@ -144,8 +142,46 @@ internal class LocalStorageAuthRepository : AuthRepository {
forgotPasswordCode = null
}

override suspend fun saveTokens(
userId: String,
accessToken: String,
refreshToken: String?,
expiresAt: Long?
) {
localStorage.setItem(KEY_ACCESS_TOKEN, accessToken)
refreshToken?.let { localStorage.setItem(KEY_REFRESH_TOKEN, it) }
?: localStorage.removeItem(KEY_REFRESH_TOKEN)
expiresAt?.let { localStorage.setItem(KEY_ACCESS_TOKEN_EXPIRES_AT, it.toString()) }
?: localStorage.removeItem(KEY_ACCESS_TOKEN_EXPIRES_AT)
}

override suspend fun getRefreshToken(): String? =
localStorage.getItem(KEY_REFRESH_TOKEN)

override suspend fun getTokenData(): TokenData? {
val accessToken = localStorage.getItem(KEY_ACCESS_TOKEN) ?: return null
return TokenData(
accessToken = accessToken,
refreshToken = localStorage.getItem(KEY_REFRESH_TOKEN),
accessTokenExpiresAt = localStorage.getItem(KEY_ACCESS_TOKEN_EXPIRES_AT)?.toLongOrNull()
)
}

override suspend fun isAccessTokenExpired(): Boolean {
val expiresAt = localStorage.getItem(KEY_ACCESS_TOKEN_EXPIRES_AT)?.toLongOrNull() ?: return false
return Clock.System.now().toEpochMilliseconds() >= expiresAt
}

override suspend fun clearTokens() {
localStorage.removeItem(KEY_ACCESS_TOKEN)
localStorage.removeItem(KEY_REFRESH_TOKEN)
localStorage.removeItem(KEY_ACCESS_TOKEN_EXPIRES_AT)
}

companion object {
private const val KEY_TOKEN = "writeopia_auth_token"
private const val KEY_ACCESS_TOKEN = "writeopia_access_token"
private const val KEY_REFRESH_TOKEN = "writeopia_refresh_token"
private const val KEY_ACCESS_TOKEN_EXPIRES_AT = "writeopia_access_token_expires_at"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
private const val KEY_USER_ID = "writeopia_user_id"
private const val KEY_USER_EMAIL = "writeopia_user_email"
private const val KEY_USER_NAME = "writeopia_user_name"
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.writeopia.auth.core.token

import io.writeopia.sdk.network.oauth.BearerTokenHandler
import io.writeopia.sdk.network.oauth.TokenRefreshResult

actual object AppBearerTokenHandler : BearerTokenHandler {
actual override suspend fun getIdToken(): String? = "mock"

actual override suspend fun getRefreshToken(): String? = null

actual override suspend fun refreshTokens(): TokenRefreshResult = TokenRefreshResult.NoRefreshToken
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.writeopia.auth.core.token

import io.writeopia.sdk.network.oauth.BearerTokenHandler
import io.writeopia.sdk.network.oauth.TokenRefreshResult

actual object AppBearerTokenHandler : BearerTokenHandler {
actual override suspend fun getIdToken(): String? = "mock"

actual override suspend fun getRefreshToken(): String? = null

actual override suspend fun refreshTokens(): TokenRefreshResult = TokenRefreshResult.NoRefreshToken
}
Loading

Back | FazBrowse Home | New Git URL