diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/api/ApiClient.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/api/ApiClient.kt index 4a18950..2f27538 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/api/ApiClient.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/api/ApiClient.kt @@ -4,6 +4,7 @@ import android.content.Context import com.collabtable.app.data.preferences.PreferencesManager import android.os.Build import android.net.Uri +import com.collabtable.app.utils.Logger import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -32,7 +33,7 @@ object ApiClient { val newRequest = if (!password.isNullOrBlank()) { request.newBuilder() - .header("Authorization", "Bearer $password") + .header("Authorization", "Bearer ${'$'}password") .build() } else { request @@ -101,11 +102,17 @@ object ApiClient { this.context = context.applicationContext val prefs = PreferencesManager.getInstance(context) baseUrl = normalizeForAndroidEmulator(prefs.getServerUrl()) + try { + Logger.i("HTTP", "API base URL initialized to $baseUrl") + } catch (_: Exception) { } retrofit = buildRetrofit() } fun setBaseUrl(url: String) { baseUrl = normalizeForAndroidEmulator(url) + try { + Logger.i("HTTP", "API base URL set to $baseUrl") + } catch (_: Exception) { } retrofit = buildRetrofit() } diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupViewModel.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupViewModel.kt index 150fe47..e2a6767 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupViewModel.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ServerSetupViewModel.kt @@ -41,7 +41,7 @@ class ServerSetupViewModel( fun validateAndSaveServerUrl( url: String, - password: String, + password: String, ) { viewModelScope.launch { _isValidating.value = true @@ -85,8 +85,9 @@ class ServerSetupViewModel( // Ensure it ends with / normalizedUrl = if (normalizedUrl.endsWith("/")) normalizedUrl else "$normalizedUrl/" - // Validate password is not empty - if (password.isBlank()) { + // Validate password is not empty (trim leading/trailing spaces) + val trimmedPassword = password.trim() + if (trimmedPassword.isBlank()) { _validationError.value = "Password cannot be empty" _isValidating.value = false return@launch @@ -178,7 +179,7 @@ class ServerSetupViewModel( val authRequest = Request.Builder() .url(testUrl) - .header("Authorization", "Bearer $password") + .header("Authorization", "Bearer ${'$'}trimmedPassword") .get() .build() @@ -191,7 +192,7 @@ class ServerSetupViewModel( if (authResponse.isSuccessful) { // Password is valid, save both URL and password preferencesManager.setServerUrl(finalUrl) - preferencesManager.setServerPassword(password) + preferencesManager.setServerPassword(trimmedPassword) // Reset sync baseline for a fresh initial sync on new server preferencesManager.clearSyncState() preferencesManager.setIsFirstRun(false) diff --git a/CollabTableServer/src/middleware/auth.ts b/CollabTableServer/src/middleware/auth.ts index 50753a9..480cb57 100644 --- a/CollabTableServer/src/middleware/auth.ts +++ b/CollabTableServer/src/middleware/auth.ts @@ -18,6 +18,12 @@ export const authenticatePassword = (req: Request, res: Response, next: NextFunc const authHeader = req.headers.authorization; if (!authHeader) { + // Log diagnostic info to help trace intermittent 401s + console.warn('[AUTH] Missing Authorization header', { + path: req.path, + method: req.method, + ip: req.ip, + }); return res.status(401).json({ error: 'Unauthorized', message: 'No authorization header provided' @@ -27,6 +33,13 @@ export const authenticatePassword = (req: Request, res: Response, next: NextFunc // Parse the authorization header const parts = authHeader.split(' '); if (parts.length !== 2 || parts[0] !== 'Bearer') { + console.warn('[AUTH] Invalid authorization header format', { + path: req.path, + method: req.method, + ip: req.ip, + // Do not log full header to avoid leaking secrets; include prefix only + headerPreview: authHeader.substring(0, Math.min(authHeader.length, 20)) + '...' + }); return res.status(401).json({ error: 'Unauthorized', message: 'Invalid authorization header format. Expected: Bearer ' @@ -37,6 +50,14 @@ export const authenticatePassword = (req: Request, res: Response, next: NextFunc // Validate password if (providedPassword !== serverPassword) { + // Avoid logging the full password; log only length and a small preview + const safePreview = providedPassword ? `${providedPassword.substring(0, 2)}…(${providedPassword.length})` : 'null'; + console.warn('[AUTH] Invalid password', { + path: req.path, + method: req.method, + ip: req.ip, + providedPreview: safePreview, + }); return res.status(401).json({ error: 'Unauthorized', message: 'Invalid password'