FIXED token issues but added logs

This commit is contained in:
Chandresh Kerkar 2025-12-20 02:20:19 +05:30
parent 88161f7933
commit d46bc1f461
3 changed files with 103 additions and 27 deletions

View File

@ -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
}
android.util.Log.d("AuthApiClient", "refreshTokens: Calling /auth/refresh endpoint")
try {
val response: RefreshResponse = client.post("http://10.0.2.2:3000/auth/refresh") {
markAsRefreshTokenRequest()
contentType(ContentType.Application.Json)
setBody(RefreshRequest(refreshToken))
}.body()
val response: RefreshResponse = client.post("http://10.0.2.2:3000/auth/refresh") { android.util.Log.d("AuthApiClient", "refreshTokens: Refresh successful, saving new tokens")
markAsRefreshTokenRequest() tokenManager.saveTokens(response.accessToken, response.refreshToken)
contentType(ContentType.Application.Json) android.util.Log.d("AuthApiClient", "refreshTokens: New tokens saved successfully")
setBody(RefreshRequest(refreshToken))
}.body()
tokenManager.saveTokens(response.accessToken, response.refreshToken) BearerTokens(response.accessToken, response.refreshToken)
} catch (e: Exception) {
BearerTokens(response.accessToken, response.refreshToken) 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 {

View File

@ -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")
} }
} }

View File

@ -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
} }
} }