fix: enhance authorization handling with improved logging for missing and invalid headers

This commit is contained in:
2025-10-27 18:27:50 +01:00
parent f48b9585ac
commit 7e77cbac32
3 changed files with 35 additions and 6 deletions
@@ -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()
}
@@ -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)
+21
View File
@@ -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 <password>'
@@ -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'