FIXED token issues but added logs
This commit is contained in:
parent
88161f7933
commit
d46bc1f461
|
|
@ -45,25 +45,40 @@ class AuthApiClient(private val context: Context) {
|
||||||
loadTokens {
|
loadTokens {
|
||||||
val accessToken = tokenManager.getAccessToken()
|
val accessToken = tokenManager.getAccessToken()
|
||||||
val refreshToken = tokenManager.getRefreshToken()
|
val refreshToken = tokenManager.getRefreshToken()
|
||||||
|
android.util.Log.d("AuthApiClient", "loadTokens: accessToken=${accessToken != null}, refreshToken=${refreshToken != null}")
|
||||||
if (accessToken != null && refreshToken != null) {
|
if (accessToken != null && refreshToken != null) {
|
||||||
|
android.util.Log.d("AuthApiClient", "loadTokens: Returning BearerTokens")
|
||||||
BearerTokens(accessToken, refreshToken)
|
BearerTokens(accessToken, refreshToken)
|
||||||
} else {
|
} else {
|
||||||
|
android.util.Log.d("AuthApiClient", "loadTokens: No tokens available, returning null")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTokens {
|
refreshTokens {
|
||||||
val refreshToken = tokenManager.getRefreshToken() ?: return@refreshTokens null
|
android.util.Log.d("AuthApiClient", "refreshTokens: Starting token refresh")
|
||||||
|
val refreshToken = tokenManager.getRefreshToken() ?: run {
|
||||||
|
android.util.Log.e("AuthApiClient", "refreshTokens: No refresh token found!")
|
||||||
|
return@refreshTokens null
|
||||||
|
}
|
||||||
|
|
||||||
val response: RefreshResponse = client.post("http://10.0.2.2:3000/auth/refresh") {
|
android.util.Log.d("AuthApiClient", "refreshTokens: Calling /auth/refresh endpoint")
|
||||||
markAsRefreshTokenRequest()
|
try {
|
||||||
contentType(ContentType.Application.Json)
|
val response: RefreshResponse = client.post("http://10.0.2.2:3000/auth/refresh") {
|
||||||
setBody(RefreshRequest(refreshToken))
|
markAsRefreshTokenRequest()
|
||||||
}.body()
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(RefreshRequest(refreshToken))
|
||||||
|
}.body()
|
||||||
|
|
||||||
tokenManager.saveTokens(response.accessToken, response.refreshToken)
|
android.util.Log.d("AuthApiClient", "refreshTokens: Refresh successful, saving new tokens")
|
||||||
|
tokenManager.saveTokens(response.accessToken, response.refreshToken)
|
||||||
|
android.util.Log.d("AuthApiClient", "refreshTokens: New tokens saved successfully")
|
||||||
|
|
||||||
BearerTokens(response.accessToken, response.refreshToken)
|
BearerTokens(response.accessToken, response.refreshToken)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("AuthApiClient", "refreshTokens: Refresh failed: ${e.message}", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,21 +170,40 @@ class AuthApiClient(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserDetails(): Result<UserDetails> = runCatching {
|
suspend fun getUserDetails(): Result<UserDetails> = runCatching {
|
||||||
client.get("users/me").body()
|
android.util.Log.d("AuthApiClient", "getUserDetails: Calling /users/me endpoint")
|
||||||
|
try {
|
||||||
|
val response = client.get("users/me")
|
||||||
|
android.util.Log.d("AuthApiClient", "getUserDetails: Response status=${response.status}")
|
||||||
|
val userDetails = response.body<UserDetails>()
|
||||||
|
android.util.Log.d("AuthApiClient", "getUserDetails: Success - user id=${userDetails.id}")
|
||||||
|
userDetails
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("AuthApiClient", "getUserDetails: Error - ${e.message}", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun refreshToken(): Result<RefreshResponse> = runCatching {
|
suspend fun refreshToken(): Result<RefreshResponse> = runCatching {
|
||||||
|
android.util.Log.d("AuthApiClient", "refreshToken: Starting manual token refresh")
|
||||||
val refreshToken = tokenManager.getRefreshToken()
|
val refreshToken = tokenManager.getRefreshToken()
|
||||||
?: throw IllegalStateException("No refresh token found")
|
?: throw IllegalStateException("No refresh token found")
|
||||||
|
|
||||||
val response: RefreshResponse = client.post("auth/refresh") {
|
android.util.Log.d("AuthApiClient", "refreshToken: Calling /auth/refresh endpoint")
|
||||||
contentType(ContentType.Application.Json)
|
try {
|
||||||
setBody(RefreshRequest(refreshToken))
|
val response: RefreshResponse = client.post("auth/refresh") {
|
||||||
}.body()
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(RefreshRequest(refreshToken))
|
||||||
|
}.body()
|
||||||
|
|
||||||
// Save the new tokens (refresh token rotates)
|
android.util.Log.d("AuthApiClient", "refreshToken: Refresh successful, saving new tokens")
|
||||||
tokenManager.saveTokens(response.accessToken, response.refreshToken)
|
// Save the new tokens (refresh token rotates)
|
||||||
response
|
tokenManager.saveTokens(response.accessToken, response.refreshToken)
|
||||||
|
android.util.Log.d("AuthApiClient", "refreshToken: New tokens saved successfully")
|
||||||
|
response
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("AuthApiClient", "refreshToken: Refresh failed: ${e.message}", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logout(): Result<LogoutResponse> = runCatching {
|
suspend fun logout(): Result<LogoutResponse> = runCatching {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.example.livingai_lg.api
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Log
|
||||||
import androidx.security.crypto.EncryptedSharedPreferences
|
import androidx.security.crypto.EncryptedSharedPreferences
|
||||||
import androidx.security.crypto.MasterKey
|
import androidx.security.crypto.MasterKey
|
||||||
|
|
||||||
|
|
@ -20,25 +21,47 @@ class TokenManager(context: Context) {
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "TokenManager"
|
||||||
private const val KEY_ACCESS_TOKEN = "access_token"
|
private const val KEY_ACCESS_TOKEN = "access_token"
|
||||||
private const val KEY_REFRESH_TOKEN = "refresh_token"
|
private const val KEY_REFRESH_TOKEN = "refresh_token"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveTokens(accessToken: String, refreshToken: String) {
|
fun saveTokens(accessToken: String, refreshToken: String) {
|
||||||
prefs.edit()
|
Log.d(TAG, "saveTokens: Saving tokens (accessToken length=${accessToken.length}, refreshToken length=${refreshToken.length})")
|
||||||
|
val success = prefs.edit()
|
||||||
.putString(KEY_ACCESS_TOKEN, accessToken)
|
.putString(KEY_ACCESS_TOKEN, accessToken)
|
||||||
.putString(KEY_REFRESH_TOKEN, refreshToken)
|
.putString(KEY_REFRESH_TOKEN, refreshToken)
|
||||||
.commit() // Use commit() instead of apply() to ensure tokens are saved synchronously
|
.commit() // Use commit() instead of apply() to ensure tokens are saved synchronously
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Log.d(TAG, "saveTokens: Tokens saved successfully")
|
||||||
|
// Verify tokens were saved
|
||||||
|
val savedAccess = prefs.getString(KEY_ACCESS_TOKEN, null)
|
||||||
|
val savedRefresh = prefs.getString(KEY_REFRESH_TOKEN, null)
|
||||||
|
Log.d(TAG, "saveTokens: Verification - accessToken saved=${savedAccess != null}, refreshToken saved=${savedRefresh != null}")
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "saveTokens: FAILED to save tokens!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAccessToken(): String? = prefs.getString(KEY_ACCESS_TOKEN, null)
|
fun getAccessToken(): String? {
|
||||||
|
val token = prefs.getString(KEY_ACCESS_TOKEN, null)
|
||||||
|
Log.d(TAG, "getAccessToken: token=${token != null}, length=${token?.length ?: 0}")
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
fun getRefreshToken(): String? = prefs.getString(KEY_REFRESH_TOKEN, null)
|
fun getRefreshToken(): String? {
|
||||||
|
val token = prefs.getString(KEY_REFRESH_TOKEN, null)
|
||||||
|
Log.d(TAG, "getRefreshToken: token=${token != null}, length=${token?.length ?: 0}")
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
fun clearTokens() {
|
fun clearTokens() {
|
||||||
|
Log.d(TAG, "clearTokens: Clearing all tokens")
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.remove(KEY_ACCESS_TOKEN)
|
.remove(KEY_ACCESS_TOKEN)
|
||||||
.remove(KEY_REFRESH_TOKEN)
|
.remove(KEY_REFRESH_TOKEN)
|
||||||
.apply()
|
.apply()
|
||||||
|
Log.d(TAG, "clearTokens: Tokens cleared")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,22 @@ class MainViewModel(context: Context) : ViewModel() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Immediately check if tokens exist (synchronous check)
|
// Immediately check if tokens exist (synchronous check)
|
||||||
val hasTokens = tokenManager.getAccessToken() != null && tokenManager.getRefreshToken() != null
|
val accessToken = tokenManager.getAccessToken()
|
||||||
|
val refreshToken = tokenManager.getRefreshToken()
|
||||||
|
val hasTokens = accessToken != null && refreshToken != null
|
||||||
|
|
||||||
|
Log.d(TAG, "MainViewModel.init: accessToken=${accessToken != null}, refreshToken=${refreshToken != null}, hasTokens=$hasTokens")
|
||||||
|
|
||||||
if (hasTokens) {
|
if (hasTokens) {
|
||||||
// Tokens exist, validate them asynchronously
|
// Tokens exist - optimistically set to Authenticated for immediate navigation
|
||||||
|
// Then validate in background (this prevents redirect to landing page on app restart)
|
||||||
|
Log.d(TAG, "MainViewModel.init: Tokens found, setting authState to Authenticated (optimistic)")
|
||||||
|
_authState.value = AuthState.Authenticated
|
||||||
|
// Validate tokens in background (this will only revert if there's a clear auth failure)
|
||||||
checkAuthStatus()
|
checkAuthStatus()
|
||||||
} else {
|
} else {
|
||||||
// No tokens, immediately set to unauthenticated
|
// No tokens, immediately set to unauthenticated
|
||||||
|
Log.d(TAG, "MainViewModel.init: No tokens found, setting authState to Unauthenticated")
|
||||||
_authState.value = AuthState.Unauthenticated
|
_authState.value = AuthState.Unauthenticated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,15 +96,17 @@ class MainViewModel(context: Context) : ViewModel() {
|
||||||
*/
|
*/
|
||||||
private fun validateTokensOptimistic() {
|
private fun validateTokensOptimistic() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
Log.d(TAG, "validateTokensOptimistic: Starting token validation")
|
||||||
// Try to fetch user details first - Ktor's Auth plugin will auto-refresh if access token is expired
|
// Try to fetch user details first - Ktor's Auth plugin will auto-refresh if access token is expired
|
||||||
authApiClient.getUserDetails()
|
authApiClient.getUserDetails()
|
||||||
.onSuccess { userDetails ->
|
.onSuccess { userDetails ->
|
||||||
// Tokens are valid, user is authenticated
|
// Tokens are valid, user is authenticated
|
||||||
Log.d(TAG, "Token validation successful - user authenticated")
|
Log.d(TAG, "validateTokensOptimistic: Token validation successful - user authenticated, userId=${userDetails.id}")
|
||||||
_authState.value = AuthState.Authenticated
|
_authState.value = AuthState.Authenticated
|
||||||
_userState.value = UserState.Success(userDetails)
|
_userState.value = UserState.Success(userDetails)
|
||||||
}
|
}
|
||||||
.onFailure { error ->
|
.onFailure { error ->
|
||||||
|
Log.w(TAG, "validateTokensOptimistic: getUserDetails failed - ${error.message}")
|
||||||
// Check if this is a network error or authentication error
|
// Check if this is a network error or authentication error
|
||||||
val isNetworkError = error.message?.contains("Unable to resolve host", ignoreCase = true) == true
|
val isNetworkError = error.message?.contains("Unable to resolve host", ignoreCase = true) == true
|
||||||
|| error.message?.contains("timeout", ignoreCase = true) == true
|
|| error.message?.contains("timeout", ignoreCase = true) == true
|
||||||
|
|
@ -104,12 +116,15 @@ class MainViewModel(context: Context) : ViewModel() {
|
||||||
|| error.message?.contains("ConnectException", ignoreCase = true) == true
|
|| error.message?.contains("ConnectException", ignoreCase = true) == true
|
||||||
|| error.message?.contains("UnknownHostException", ignoreCase = true) == true
|
|| error.message?.contains("UnknownHostException", ignoreCase = true) == true
|
||||||
|
|
||||||
|
Log.d(TAG, "validateTokensOptimistic: isNetworkError=$isNetworkError")
|
||||||
|
|
||||||
if (isNetworkError) {
|
if (isNetworkError) {
|
||||||
// Network error - keep optimistic authentication state
|
// Network error - keep optimistic authentication state
|
||||||
// User might be offline, tokens are still valid
|
// User might be offline, tokens are still valid
|
||||||
Log.w(TAG, "Network error during token validation (keeping optimistic auth): ${error.message}")
|
Log.w(TAG, "validateTokensOptimistic: Network error detected (keeping optimistic auth): ${error.message}")
|
||||||
_userState.value = UserState.Error("Network error. Please check your connection.")
|
_userState.value = UserState.Error("Network error. Please check your connection.")
|
||||||
// Keep authState as Authenticated - don't revert on network errors
|
// Keep authState as Authenticated - don't revert on network errors
|
||||||
|
Log.d(TAG, "validateTokensOptimistic: Keeping authState as Authenticated despite network error")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,12 +205,16 @@ class MainViewModel(context: Context) : ViewModel() {
|
||||||
val accessToken = tokenManager.getAccessToken()
|
val accessToken = tokenManager.getAccessToken()
|
||||||
val refreshToken = tokenManager.getRefreshToken()
|
val refreshToken = tokenManager.getRefreshToken()
|
||||||
|
|
||||||
|
Log.d(TAG, "checkAuthStatus: accessToken=${accessToken != null}, refreshToken=${refreshToken != null}")
|
||||||
|
|
||||||
if (accessToken != null && refreshToken != null) {
|
if (accessToken != null && refreshToken != null) {
|
||||||
// Tokens exist, validate them by fetching user details
|
// Tokens exist, validate them using optimistic validation
|
||||||
// The Ktor Auth plugin will automatically refresh if access token is expired
|
// This keeps authState as Authenticated unless there's a clear auth failure
|
||||||
validateTokens()
|
Log.d(TAG, "checkAuthStatus: Validating tokens optimistically")
|
||||||
|
validateTokensOptimistic()
|
||||||
} else {
|
} else {
|
||||||
// No tokens, user is not authenticated
|
// No tokens, user is not authenticated
|
||||||
|
Log.d(TAG, "checkAuthStatus: No tokens found, setting authState to Unauthenticated")
|
||||||
_authState.value = AuthState.Unauthenticated
|
_authState.value = AuthState.Unauthenticated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue