feat(android): centralize mobile session refresh and password change
This commit is contained in:
+98
@@ -0,0 +1,98 @@
|
||||
package com.korexlabs.dhinspeccion.data
|
||||
|
||||
import android.content.Context
|
||||
import com.korexlabs.dhinspeccion.BuildConfig
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
|
||||
data class ChangeMobilePasswordRequest(
|
||||
val currentPassword: String,
|
||||
val newPassword: String,
|
||||
)
|
||||
|
||||
data class ChangeMobilePasswordResponse(
|
||||
val status: String,
|
||||
val mustChangePassword: Boolean,
|
||||
)
|
||||
|
||||
private interface MobileSessionApi {
|
||||
@POST("auth/mobile/refresh")
|
||||
suspend fun refresh(@Body request: RefreshRequest): MobileSessionResponse
|
||||
|
||||
@POST("auth/change-password")
|
||||
suspend fun changePassword(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Body request: ChangeMobilePasswordRequest,
|
||||
): ChangeMobilePasswordResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* Único coordinador de sesión del proceso Android.
|
||||
*
|
||||
* Todos los repositorios comparten el mismo gate de refresh para que un token rotativo no sea
|
||||
* consumido en paralelo por Inventario, Hallazgos y Actas.
|
||||
*/
|
||||
class MobileSessionCoordinator private constructor(context: Context) {
|
||||
private val store = SecureSessionStore(context.applicationContext)
|
||||
private val gate = SessionRefreshGate()
|
||||
private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
|
||||
private val api: MobileSessionApi = Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.API_BASE_URL)
|
||||
.client(OkHttpClient.Builder().build())
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.build()
|
||||
.create(MobileSessionApi::class.java)
|
||||
|
||||
fun currentSession(): StoredSession? = store.load()
|
||||
|
||||
fun save(response: MobileSessionResponse): StoredSession = store.save(response)
|
||||
|
||||
fun clear() = store.clear()
|
||||
|
||||
suspend fun <T> authorized(block: suspend (StoredSession) -> T): T {
|
||||
var session = store.load() ?: throw IllegalStateException("Sesión no iniciada")
|
||||
try {
|
||||
return block(session)
|
||||
} catch (error: HttpException) {
|
||||
if (error.code() != 401) throw error
|
||||
}
|
||||
|
||||
session = gate.refreshIfNeeded(
|
||||
previousRefreshToken = session.refreshToken,
|
||||
load = store::load,
|
||||
save = store::save,
|
||||
clear = store::clear,
|
||||
refresh = { token -> api.refresh(RefreshRequest(token)) },
|
||||
)
|
||||
return block(session)
|
||||
}
|
||||
|
||||
suspend fun changePassword(currentPassword: String, newPassword: String): StoredSession {
|
||||
val sessionUsed = authorized { session ->
|
||||
api.changePassword(
|
||||
authorization = "Bearer ${session.accessToken}",
|
||||
request = ChangeMobilePasswordRequest(currentPassword, newPassword),
|
||||
)
|
||||
session
|
||||
}
|
||||
val latest = store.load() ?: sessionUsed
|
||||
return store.save(latest.copy(mustChangePassword = false))
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var instance: MobileSessionCoordinator? = null
|
||||
|
||||
fun get(context: Context): MobileSessionCoordinator =
|
||||
instance ?: synchronized(this) {
|
||||
instance ?: MobileSessionCoordinator(context.applicationContext).also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user