Skip to content

Commit

Permalink
πŸ”€ :: (#514) google app μ•„ν‚€ν…μ²˜λ‘œ λ³€κ²½
Browse files Browse the repository at this point in the history
πŸ”€ :: (#514) google app μ•„ν‚€ν…μ²˜λ‘œ λ³€κ²½
  • Loading branch information
JunJaBoy authored Oct 17, 2023
2 parents f573b3c + 070ae35 commit acb36ae
Show file tree
Hide file tree
Showing 59 changed files with 611 additions and 617 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import team.aliens.dms.android.core.jwt.network.IgnoreRequests
import team.aliens.dms.android.core.network.HttpMethod
import team.aliens.dms.android.core.network.HttpRequest
import team.aliens.dms.android.core.network.di.BaseUrl
import javax.inject.Singleton

Expand All @@ -20,4 +23,73 @@ object NetworkConfigModule {
@Provides
@Singleton
fun provideTokenReissueUrl(@BaseUrl baseUrl: String): String = "$baseUrl/auth/reissue"

@Provides
@Singleton
fun provideIgnoreRequests(): IgnoreRequests = object : IgnoreRequests {
override val requests: List<HttpRequest> = listOf(

// Auth
HttpRequest(
method = HttpMethod.POST,
path = "/auth/tokens",
),
HttpRequest(
method = HttpMethod.POST,
path = "/auth/code",
),
HttpRequest(
method = HttpMethod.GET,
path = "/auth/code",
),
HttpRequest(
method = HttpMethod.GET,
path = "/auth/account-id",
),

// Student
HttpRequest(
method = HttpMethod.POST,
path = "/students/signup",
),
HttpRequest(
method = HttpMethod.GET,
path = "/students/name",
),
HttpRequest(
method = HttpMethod.GET,
path = "/students/account-id/",
),
HttpRequest(
method = HttpMethod.PATCH,
path = "/students/password/initialization",
),
HttpRequest(
method = HttpMethod.GET,
path = "/students/account-id/duplication",
),
HttpRequest(
method = HttpMethod.GET,
path = "/students/email/duplication",
),

// School
HttpRequest(
method = HttpMethod.GET,
path = "/schools",
),
HttpRequest(
method = HttpMethod.GET,
path = "/schools/question/",
),
HttpRequest(
method = HttpMethod.GET,
path = "/schools/answer/",
),
HttpRequest(
method = HttpMethod.GET,
path = "/schools/code",
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.room.TypeConverter
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZoneOffset
import team.aliens.dms.android.shared.date.extension.toLocalDate
import team.aliens.dms.android.shared.date.extension.toLocalDateTime
import team.aliens.dms.android.shared.date.toLocalDate
import team.aliens.dms.android.shared.date.toLocalDateTime
import javax.inject.Inject

@ProvidedTypeConverter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package team.aliens.dms.android.core.datastore.exception

open class LoadFailureException(message: String? = "Load failure") : DataStoreException(message)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package team.aliens.dms.android.core.datastore.util
import team.aliens.dms.android.core.datastore.exception.TransformFailureException

suspend inline fun <reified T> transform(
crossinline job: suspend () -> T,
onSuccess: (T) -> Unit = {},
onFailure: (Throwable) -> Unit = { throw TransformFailureException() },
) = runCatching { job() }
crossinline block: suspend () -> T,
) = runCatching { block() }
.onSuccess(onSuccess)
.onFailure(onFailure)
.getOrThrow()
Original file line number Diff line number Diff line change
@@ -1,16 +1,66 @@
package team.aliens.dms.android.core.jwt

import team.aliens.dms.android.core.jwt.store.JwtStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import team.aliens.dms.android.core.datastore.exception.LoadFailureException
import team.aliens.dms.android.core.jwt.datastore.JwtDataStoreDataSource
import team.aliens.dms.android.core.jwt.exception.CannotUseTokensException
import team.aliens.dms.android.core.jwt.network.TokenReissueManager
import team.aliens.dms.android.shared.date.util.now
import javax.inject.Inject

// TODO: JWT Repository κ΅¬ν˜„ κ³ λ―Ό
object JwtProvider : JwtProviderInjectionDelegation() {
/*
val cachedAccessToken: AccessToken
val cachedRefreshToken: RefreshToken
*/

private var _cachedAccessToken: AccessToken? = null
val cachedAccessToken: AccessToken
get() = if (isCachedAccessTokenAvailable) {
_cachedAccessToken!!
} else {
this.fetchTokens().accessToken
}

private var _cachedAccessTokenExpiration: AccessTokenExpiration? = null
val cachedAccessTokenExpiration: AccessTokenExpiration
get() = _cachedAccessTokenExpiration ?: this.fetchTokens().accessTokenExpiration

private val isCachedAccessTokenAvailable: Boolean
get() {
val con1 = _cachedAccessToken != null
val con2 = now.isBefore(cachedAccessTokenExpiration)
return con1 && con2
}

init {
loadTokens()
}

private fun loadTokens(): Tokens = try {
jwtDataStoreDataSource.loadTokens().also(::updateTokens)
} catch (e: LoadFailureException) {
throw CannotUseTokensException()
}

private fun fetchTokens(): Tokens = try {
val refreshToken = jwtDataStoreDataSource.loadRefreshToken()
tokenReissueManager(refreshToken).toModel().also(::updateTokens)
} catch (e: LoadFailureException) {
throw CannotUseTokensException()
}

private fun updateTokens(tokens: Tokens) {
this._cachedAccessToken = tokens.accessToken
this._cachedAccessTokenExpiration = tokens.accessTokenExpiration
CoroutineScope(Dispatchers.IO).launch { jwtDataStoreDataSource.storeTokens(tokens) }
}
}

abstract class JwtProviderInjectionDelegation {

@Inject
lateinit var jwtDataStoreDataSource: JwtDataStoreDataSource

@Inject
lateinit var jwtStore: JwtStore
lateinit var tokenReissueManager: TokenReissueManager
}
17 changes: 17 additions & 0 deletions core/jwt/src/main/java/team/aliens/dms/android/core/jwt/Tokens.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package team.aliens.dms.android.core.jwt

import team.aliens.dms.android.core.jwt.network.model.TokensResponse

data class Tokens(
val accessToken: AccessToken,
val accessTokenExpiration: AccessTokenExpiration,
val refreshToken: RefreshToken,
val refreshTokenExpiration: RefreshTokenExpiration,
)

internal fun TokensResponse.toModel(): Tokens = Tokens(
accessToken = this.accessToken,
accessTokenExpiration = this.accessTokenExpiration,
refreshToken = this.refreshToken,
refreshTokenExpiration = this.refreshTokenExpiration,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@ import team.aliens.dms.android.core.jwt.AccessToken
import team.aliens.dms.android.core.jwt.AccessTokenExpiration
import team.aliens.dms.android.core.jwt.RefreshToken
import team.aliens.dms.android.core.jwt.RefreshTokenExpiration
import team.aliens.dms.android.core.jwt.Tokens

abstract class JwtDataStoreDataSource {

abstract fun loadTokens(): Tokens

abstract suspend fun storeTokens(tokens: Tokens)

internal abstract class JwtDataStoreDataSource {
abstract fun loadAccessToken(): AccessToken

abstract suspend fun storeAccessToken(token: AccessToken)

abstract fun loadAccessTokenExpiration(): AccessTokenExpiration

abstract suspend fun storeAccessTokenExpiration(expiration: AccessTokenExpiration)

abstract fun loadRefreshToken(): RefreshToken

abstract suspend fun storeRefreshToken(token: RefreshToken)

abstract fun loadRefreshTokenExpiration(): RefreshTokenExpiration

abstract suspend fun storeRefreshTokenExpiration(expiration: RefreshTokenExpiration)
}
Original file line number Diff line number Diff line change
@@ -1,110 +1,46 @@
package team.aliens.dms.android.core.jwt.datastore

import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import team.aliens.dms.android.core.datastore.PreferencesDataStore
import team.aliens.dms.android.core.datastore.util.transform
import team.aliens.dms.android.core.jwt.AccessToken
import team.aliens.dms.android.core.jwt.AccessTokenExpiration
import team.aliens.dms.android.core.jwt.RefreshToken
import team.aliens.dms.android.core.jwt.RefreshTokenExpiration
import team.aliens.dms.android.core.jwt.exception.AccessTokenExpirationNotFoundException
import team.aliens.dms.android.core.jwt.exception.AccessTokenNotFoundException
import team.aliens.dms.android.core.jwt.exception.CannotStoreAccessTokenException
import team.aliens.dms.android.core.jwt.exception.CannotStoreAccessTokenExpirationException
import team.aliens.dms.android.core.jwt.exception.CannotStoreRefreshTokenException
import team.aliens.dms.android.core.jwt.exception.CannotStoreRefreshTokenExpirationException
import team.aliens.dms.android.core.jwt.exception.RefreshTokenExpirationNotFoundException
import team.aliens.dms.android.core.jwt.exception.RefreshTokenNotFoundException
import team.aliens.dms.android.shared.date.extension.toEpochMilli
import team.aliens.dms.android.shared.date.extension.toLocalDateTime
import team.aliens.dms.android.core.jwt.Tokens
import team.aliens.dms.android.core.jwt.datastore.store.JwtStore
import javax.inject.Inject

internal class JwtDataStoreDataSourceImpl @Inject constructor(
private val preferencesDataStore: PreferencesDataStore,
private val jwtStore: JwtStore,
) : JwtDataStoreDataSource() {
override fun loadAccessToken(): AccessToken = runBlocking {
preferencesDataStore.data.map { preferences ->
preferences[ACCESS_TOKEN] ?: throw AccessTokenNotFoundException()
}.first()
}

override suspend fun storeAccessToken(token: AccessToken) {
transform(
job = {
preferencesDataStore.edit { preferences ->
preferences[ACCESS_TOKEN] = token
}
},
onFailure = { throw CannotStoreAccessTokenException() },
)
override fun loadTokens(): Tokens = jwtStore.loadTokens()

override suspend fun storeTokens(tokens: Tokens) {
jwtStore.storeTokens(tokens)
}

override fun loadAccessTokenExpiration(): AccessTokenExpiration = runBlocking {
preferencesDataStore.data.map { preferences ->
val longValue = preferences[ACCESS_TOKEN_EXPIRATION]
?: throw AccessTokenExpirationNotFoundException()
override fun loadAccessToken(): AccessToken = jwtStore.loadAccessToken()

return@map longValue.toLocalDateTime()
}.first()
override suspend fun storeAccessToken(token: AccessToken) {
jwtStore.storeAccessToken(token)
}

override fun loadAccessTokenExpiration(): AccessTokenExpiration =
jwtStore.loadAccessTokenExpiration()

override suspend fun storeAccessTokenExpiration(expiration: AccessTokenExpiration) {
transform(
job = {
preferencesDataStore.edit { preferences ->
preferences[ACCESS_TOKEN_EXPIRATION] = expiration.toEpochMilli()
}
},
onFailure = { throw CannotStoreAccessTokenExpirationException() },
)
jwtStore.storeAccessTokenExpiration(expiration)
}

override fun loadRefreshToken(): RefreshToken = runBlocking {
preferencesDataStore.data.map { preferences ->
preferences[REFRESH_TOKEN] ?: throw RefreshTokenNotFoundException()
}.first()
}
override fun loadRefreshToken(): RefreshToken = jwtStore.loadRefreshToken()

override suspend fun storeRefreshToken(token: RefreshToken) {
transform(
job = {
preferencesDataStore.edit { preferences ->
preferences[REFRESH_TOKEN] = token
}
},
onFailure = { throw CannotStoreRefreshTokenException() },
)
jwtStore.storeRefreshToken(token)
}

override fun loadRefreshTokenExpiration(): RefreshTokenExpiration = runBlocking {
preferencesDataStore.data.map { preferences ->
val longValue = preferences[REFRESH_TOKEN_EXPIRATION]
?: throw RefreshTokenExpirationNotFoundException()

return@map longValue.toLocalDateTime()
}.first()
}
override fun loadRefreshTokenExpiration(): RefreshTokenExpiration =
jwtStore.loadRefreshTokenExpiration()

override suspend fun storeRefreshTokenExpiration(expiration: RefreshTokenExpiration) {
transform(
job = {
preferencesDataStore.edit { preferences ->
preferences[REFRESH_TOKEN_EXPIRATION] = expiration.toEpochMilli()
}
},
onFailure = { throw CannotStoreRefreshTokenExpirationException() },
)
}

private companion object {
val ACCESS_TOKEN = stringPreferencesKey("access-token")
val ACCESS_TOKEN_EXPIRATION = longPreferencesKey("access-token-expiration")
val REFRESH_TOKEN = stringPreferencesKey("refresh-token")
val REFRESH_TOKEN_EXPIRATION = longPreferencesKey("refresh-token-expiration")
jwtStore.storeRefreshTokenExpiration(expiration)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package team.aliens.dms.android.core.jwt.datastore.store

import team.aliens.dms.android.core.jwt.AccessToken
import team.aliens.dms.android.core.jwt.AccessTokenExpiration
import team.aliens.dms.android.core.jwt.RefreshToken
import team.aliens.dms.android.core.jwt.RefreshTokenExpiration
import team.aliens.dms.android.core.jwt.Tokens

internal abstract class JwtStore {

abstract fun loadTokens(): Tokens

abstract suspend fun storeTokens(tokens: Tokens)

abstract fun loadAccessToken(): AccessToken

abstract suspend fun storeAccessToken(token: AccessToken)

abstract fun loadAccessTokenExpiration(): AccessTokenExpiration

abstract suspend fun storeAccessTokenExpiration(expiration: AccessTokenExpiration)

abstract fun loadRefreshToken(): RefreshToken

abstract suspend fun storeRefreshToken(token: RefreshToken)

abstract fun loadRefreshTokenExpiration(): RefreshTokenExpiration

abstract suspend fun storeRefreshTokenExpiration(expiration: RefreshTokenExpiration)
}
Loading

0 comments on commit acb36ae

Please sign in to comment.