From 062af3b2dad804287a52d929f8da1cd9a87743f5 Mon Sep 17 00:00:00 2001 From: Simon Franken Date: Thu, 19 Feb 2026 18:58:31 +0100 Subject: [PATCH] Add AuthManager OSLog tracing and fix URL construction in APIEndpoints --- .../TimeTracker/Core/Auth/AuthManager.swift | 12 +++++++++++- .../TimeTracker/Core/Network/APIEndpoints.swift | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift b/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift index 0be4157..83985f1 100644 --- a/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift +++ b/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift @@ -1,5 +1,8 @@ import Foundation import KeychainAccess +import OSLog + +private let logger = Logger(subsystem: "com.timetracker.app", category: "AuthManager") @MainActor final class AuthManager: ObservableObject { @@ -29,19 +32,23 @@ final class AuthManager: ObservableObject { } func checkAuthState() async { - guard accessToken != nil else { + guard let token = accessToken else { + logger.info("checkAuthState — no token in keychain, not authenticated") isAuthenticated = false return } + logger.info("checkAuthState — token found (first 20 chars: \(token.prefix(20))…), calling /auth/me") do { let user: User = try await apiClient.request( endpoint: APIEndpoint.me, authenticated: true ) + logger.info("checkAuthState — /auth/me OK, user: \(user.id)") currentUser = user isAuthenticated = true } catch { + logger.error("checkAuthState — /auth/me failed: \(error.localizedDescription) — clearing auth") clearAuth() } } @@ -67,15 +74,18 @@ final class AuthManager: ObservableObject { } func clearAuth() { + logger.info("clearAuth — wiping token and user") accessToken = nil currentUser = nil isAuthenticated = false } func handleTokenResponse(_ response: TokenResponse) async { + logger.info("handleTokenResponse — storing JWT for user \(response.user.id)") accessToken = response.accessToken currentUser = response.user isAuthenticated = true + logger.info("handleTokenResponse — isAuthenticated = true, token stored: \(self.accessToken != nil)") } var loginURL: URL { diff --git a/ios/TimeTracker/TimeTracker/Core/Network/APIEndpoints.swift b/ios/TimeTracker/TimeTracker/Core/Network/APIEndpoints.swift index 6def79d..accdc49 100644 --- a/ios/TimeTracker/TimeTracker/Core/Network/APIEndpoints.swift +++ b/ios/TimeTracker/TimeTracker/Core/Network/APIEndpoints.swift @@ -29,6 +29,13 @@ enum APIEndpoint { struct APIEndpoints { static func url(for endpoint: String) -> URL { - AppConfig.apiBaseURL.appendingPathComponent(endpoint) + // Use URL(string:relativeTo:) rather than appendingPathComponent so that + // leading slashes in endpoint strings are handled correctly and don't + // accidentally replace or duplicate the base URL path. + let base = AppConfig.apiBaseURL.absoluteString.hasSuffix("/") + ? AppConfig.apiBaseURL + : URL(string: AppConfig.apiBaseURL.absoluteString + "/")! + let relative = endpoint.hasPrefix("/") ? String(endpoint.dropFirst()) : endpoint + return URL(string: relative, relativeTo: base)!.absoluteURL } }