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 {
val accessToken = tokenManager.getAccessToken()
val refreshToken = tokenManager.getRefreshToken()
android.util.Log.d("AuthApiClient", "loadTokens: accessToken=${accessToken != null}, refreshToken=${refreshToken != null}")
if (accessToken != null && refreshToken != null) {
android.util.Log.d("AuthApiClient", "loadTokens: Returning BearerTokens")
BearerTokens(accessToken, refreshToken)
} else {
android.util.Log.d("AuthApiClient", "loadTokens: No tokens available, returning null")
null
}
}
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") {
markAsRefreshTokenRequest()
contentType(ContentType.Application.Json)
setBody(RefreshRequest(refreshToken))
}.body()
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")
tokenManager.saveTokens(response.accessToken, response.refreshToken)
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 {
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 {
android.util.Log.d("AuthApiClient", "refreshToken: Starting manual token refresh")
val refreshToken = tokenManager.getRefreshToken()
?: throw IllegalStateException("No refresh token found")
val response: RefreshResponse = client.post("auth/refresh") {
contentType(ContentType.Application.Json)
setBody(RefreshRequest(refreshToken))
}.body()
android.util.Log.d("AuthApiClient", "refreshToken: Calling /auth/refresh endpoint")
try {
val response: RefreshResponse = client.post("auth/refresh") {
contentType(ContentType.Application.Json)
setBody(RefreshRequest(refreshToken))
}.body()
// Save the new tokens (refresh token rotates)
tokenManager.saveTokens(response.accessToken, response.refreshToken)
response
android.util.Log.d("AuthApiClient", "refreshToken: Refresh successful, saving new tokens")
// Save the new tokens (refresh token rotates)
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 {

View File

@ -2,6 +2,7 @@ package com.example.livingai_lg.api
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
@ -20,25 +21,47 @@ class TokenManager(context: Context) {
)
companion object {
private const val TAG = "TokenManager"
private const val KEY_ACCESS_TOKEN = "access_token"
private const val KEY_REFRESH_TOKEN = "refresh_token"
}
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_REFRESH_TOKEN, refreshToken)
.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() {
Log.d(TAG, "clearTokens: Clearing all tokens")
prefs.edit()
.remove(KEY_ACCESS_TOKEN)
.remove(KEY_REFRESH_TOKEN)
.apply()
Log.d(TAG, "clearTokens: Tokens cleared")
}
}

View File

@ -38,12 +38,22 @@ class MainViewModel(context: Context) : ViewModel() {
init {
// 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) {
// 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()
} else {
// No tokens, immediately set to unauthenticated
Log.d(TAG, "MainViewModel.init: No tokens found, setting authState to Unauthenticated")
_authState.value = AuthState.Unauthenticated
}
}
@ -86,15 +96,17 @@ class MainViewModel(context: Context) : ViewModel() {
*/
private fun validateTokensOptimistic() {
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
authApiClient.getUserDetails()
.onSuccess { userDetails ->
// 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
_userState.value = UserState.Success(userDetails)
}
.onFailure { error ->
Log.w(TAG, "validateTokensOptimistic: getUserDetails failed - ${error.message}")
// Check if this is a network error or authentication error
val isNetworkError = error.message?.contains("Unable to resolve host", 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("UnknownHostException", ignoreCase = true) == true
Log.d(TAG, "validateTokensOptimistic: isNetworkError=$isNetworkError")
if (isNetworkError) {
// Network error - keep optimistic authentication state
// 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.")
// Keep authState as Authenticated - don't revert on network errors
Log.d(TAG, "validateTokensOptimistic: Keeping authState as Authenticated despite network error")
return@launch
}
@ -190,12 +205,16 @@ class MainViewModel(context: Context) : ViewModel() {
val accessToken = tokenManager.getAccessToken()
val refreshToken = tokenManager.getRefreshToken()
Log.d(TAG, "checkAuthStatus: accessToken=${accessToken != null}, refreshToken=${refreshToken != null}")
if (accessToken != null && refreshToken != null) {
// Tokens exist, validate them by fetching user details
// The Ktor Auth plugin will automatically refresh if access token is expired
validateTokens()
// Tokens exist, validate them using optimistic validation
// This keeps authState as Authenticated unless there's a clear auth failure
Log.d(TAG, "checkAuthStatus: Validating tokens optimistically")
validateTokensOptimistic()
} else {
// No tokens, user is not authenticated
Log.d(TAG, "checkAuthStatus: No tokens found, setting authState to Unauthenticated")
_authState.value = AuthState.Unauthenticated
}
}