fix: enhance authorization handling with improved logging for missing and invalid headers
This commit is contained in:
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import com.collabtable.app.data.preferences.PreferencesManager
|
import com.collabtable.app.data.preferences.PreferencesManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.collabtable.app.utils.Logger
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
@@ -32,7 +33,7 @@ object ApiClient {
|
|||||||
val newRequest =
|
val newRequest =
|
||||||
if (!password.isNullOrBlank()) {
|
if (!password.isNullOrBlank()) {
|
||||||
request.newBuilder()
|
request.newBuilder()
|
||||||
.header("Authorization", "Bearer $password")
|
.header("Authorization", "Bearer ${'$'}password")
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
request
|
request
|
||||||
@@ -101,11 +102,17 @@ object ApiClient {
|
|||||||
this.context = context.applicationContext
|
this.context = context.applicationContext
|
||||||
val prefs = PreferencesManager.getInstance(context)
|
val prefs = PreferencesManager.getInstance(context)
|
||||||
baseUrl = normalizeForAndroidEmulator(prefs.getServerUrl())
|
baseUrl = normalizeForAndroidEmulator(prefs.getServerUrl())
|
||||||
|
try {
|
||||||
|
Logger.i("HTTP", "API base URL initialized to $baseUrl")
|
||||||
|
} catch (_: Exception) { }
|
||||||
retrofit = buildRetrofit()
|
retrofit = buildRetrofit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBaseUrl(url: String) {
|
fun setBaseUrl(url: String) {
|
||||||
baseUrl = normalizeForAndroidEmulator(url)
|
baseUrl = normalizeForAndroidEmulator(url)
|
||||||
|
try {
|
||||||
|
Logger.i("HTTP", "API base URL set to $baseUrl")
|
||||||
|
} catch (_: Exception) { }
|
||||||
retrofit = buildRetrofit()
|
retrofit = buildRetrofit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -85,8 +85,9 @@ class ServerSetupViewModel(
|
|||||||
// Ensure it ends with /
|
// Ensure it ends with /
|
||||||
normalizedUrl = if (normalizedUrl.endsWith("/")) normalizedUrl else "$normalizedUrl/"
|
normalizedUrl = if (normalizedUrl.endsWith("/")) normalizedUrl else "$normalizedUrl/"
|
||||||
|
|
||||||
// Validate password is not empty
|
// Validate password is not empty (trim leading/trailing spaces)
|
||||||
if (password.isBlank()) {
|
val trimmedPassword = password.trim()
|
||||||
|
if (trimmedPassword.isBlank()) {
|
||||||
_validationError.value = "Password cannot be empty"
|
_validationError.value = "Password cannot be empty"
|
||||||
_isValidating.value = false
|
_isValidating.value = false
|
||||||
return@launch
|
return@launch
|
||||||
@@ -178,7 +179,7 @@ class ServerSetupViewModel(
|
|||||||
val authRequest =
|
val authRequest =
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(testUrl)
|
.url(testUrl)
|
||||||
.header("Authorization", "Bearer $password")
|
.header("Authorization", "Bearer ${'$'}trimmedPassword")
|
||||||
.get()
|
.get()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -191,7 +192,7 @@ class ServerSetupViewModel(
|
|||||||
if (authResponse.isSuccessful) {
|
if (authResponse.isSuccessful) {
|
||||||
// Password is valid, save both URL and password
|
// Password is valid, save both URL and password
|
||||||
preferencesManager.setServerUrl(finalUrl)
|
preferencesManager.setServerUrl(finalUrl)
|
||||||
preferencesManager.setServerPassword(password)
|
preferencesManager.setServerPassword(trimmedPassword)
|
||||||
// Reset sync baseline for a fresh initial sync on new server
|
// Reset sync baseline for a fresh initial sync on new server
|
||||||
preferencesManager.clearSyncState()
|
preferencesManager.clearSyncState()
|
||||||
preferencesManager.setIsFirstRun(false)
|
preferencesManager.setIsFirstRun(false)
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ export const authenticatePassword = (req: Request, res: Response, next: NextFunc
|
|||||||
const authHeader = req.headers.authorization;
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
if (!authHeader) {
|
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({
|
return res.status(401).json({
|
||||||
error: 'Unauthorized',
|
error: 'Unauthorized',
|
||||||
message: 'No authorization header provided'
|
message: 'No authorization header provided'
|
||||||
@@ -27,6 +33,13 @@ export const authenticatePassword = (req: Request, res: Response, next: NextFunc
|
|||||||
// Parse the authorization header
|
// Parse the authorization header
|
||||||
const parts = authHeader.split(' ');
|
const parts = authHeader.split(' ');
|
||||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
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({
|
return res.status(401).json({
|
||||||
error: 'Unauthorized',
|
error: 'Unauthorized',
|
||||||
message: 'Invalid authorization header format. Expected: Bearer <password>'
|
message: 'Invalid authorization header format. Expected: Bearer <password>'
|
||||||
@@ -37,6 +50,14 @@ export const authenticatePassword = (req: Request, res: Response, next: NextFunc
|
|||||||
|
|
||||||
// Validate password
|
// Validate password
|
||||||
if (providedPassword !== serverPassword) {
|
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({
|
return res.status(401).json({
|
||||||
error: 'Unauthorized',
|
error: 'Unauthorized',
|
||||||
message: 'Invalid password'
|
message: 'Invalid password'
|
||||||
|
|||||||
Reference in New Issue
Block a user