Refactor AuthClient and HttpClient to allow coexistence of API key and JWT; implement logoutUser method to clear JWT while preserving API key, enhancing user logout functionality. Improve header management in HttpClient for database operations and add debug logging for request headers.

This commit is contained in:
anonpenguin23 2025-10-28 13:35:54 +02:00
parent dcf8efe428
commit 76bb82d4f8
2 changed files with 107 additions and 10 deletions

View File

@ -28,14 +28,14 @@ export class AuthClient {
setApiKey(apiKey: string) {
this.currentApiKey = apiKey;
this.currentJwt = undefined;
// Don't clear JWT - it will be cleared explicitly on logout
this.httpClient.setApiKey(apiKey);
this.storage.set("apiKey", apiKey);
}
setJwt(jwt: string) {
this.currentJwt = jwt;
this.currentApiKey = undefined;
// Don't clear API key - keep it as fallback for after logout
this.httpClient.setJwt(jwt);
this.storage.set("jwt", jwt);
}
@ -62,6 +62,51 @@ export class AuthClient {
return token;
}
/**
* Logout user and clear JWT, but preserve API key
* Use this for user logout in apps where API key is app-level credential
*/
async logoutUser(): Promise<void> {
// Attempt server-side logout if using JWT
if (this.currentJwt) {
try {
await this.httpClient.post("/v1/auth/logout", { all: true });
} catch (error) {
// Log warning but don't fail - local cleanup is more important
console.warn(
"Server-side logout failed, continuing with local cleanup:",
error
);
}
}
// Clear JWT only, preserve API key
this.currentJwt = undefined;
this.httpClient.setJwt(undefined);
await this.storage.set("jwt", ""); // Clear JWT from storage
// Ensure API key is loaded and set as active auth method
if (!this.currentApiKey) {
// Try to load from storage
const storedApiKey = await this.storage.get("apiKey");
if (storedApiKey) {
this.currentApiKey = storedApiKey;
}
}
// Restore API key as the active auth method
if (this.currentApiKey) {
this.httpClient.setApiKey(this.currentApiKey);
console.log("[Auth] API key restored after user logout");
} else {
console.warn("[Auth] No API key available after logout");
}
}
/**
* Full logout - clears both JWT and API key
* Use this to completely reset authentication state
*/
async logout(): Promise<void> {
// Only attempt server-side logout if using JWT
// API keys don't support server-side logout with all=true

View File

@ -27,20 +27,53 @@ export class HttpClient {
setApiKey(apiKey?: string) {
this.apiKey = apiKey;
this.jwt = undefined;
// Don't clear JWT - allow both to coexist
if (typeof console !== "undefined") {
console.log(
"[HttpClient] API key set:",
!!apiKey,
"JWT still present:",
!!this.jwt
);
}
}
setJwt(jwt?: string) {
this.jwt = jwt;
this.apiKey = undefined;
// Don't clear API key - allow both to coexist
if (typeof console !== "undefined") {
console.log(
"[HttpClient] JWT set:",
!!jwt,
"API key still present:",
!!this.apiKey
);
}
}
private getAuthHeaders(): Record<string, string> {
private getAuthHeaders(path: string): Record<string, string> {
const headers: Record<string, string> = {};
if (this.jwt) {
headers["Authorization"] = `Bearer ${this.jwt}`;
} else if (this.apiKey) {
headers["X-API-Key"] = this.apiKey;
// For database operations, ONLY use API key to avoid JWT user context
// interfering with namespace-level authorization
const isDbOperation = path.includes("/v1/rqlite/");
if (isDbOperation) {
// For database operations: use only API key (preferred for namespace operations)
if (this.apiKey) {
headers["X-API-Key"] = this.apiKey;
} else if (this.jwt) {
// Fallback to JWT if no API key
headers["Authorization"] = `Bearer ${this.jwt}`;
}
} else {
// For auth/other operations: send both JWT and API key
if (this.jwt) {
headers["Authorization"] = `Bearer ${this.jwt}`;
}
if (this.apiKey) {
headers["X-API-Key"] = this.apiKey;
}
}
return headers;
}
@ -68,10 +101,29 @@ export class HttpClient {
const headers: Record<string, string> = {
"Content-Type": "application/json",
...this.getAuthHeaders(),
...this.getAuthHeaders(path),
...options.headers,
};
// Debug: Log headers being sent
if (
typeof console !== "undefined" &&
(path.includes("/db/") ||
path.includes("/query") ||
path.includes("/auth/"))
) {
console.log("[HttpClient] Request headers for", path, {
hasAuth: !!headers["Authorization"],
hasApiKey: !!headers["X-API-Key"],
authPrefix: headers["Authorization"]
? headers["Authorization"].substring(0, 20)
: "none",
apiKeyPrefix: headers["X-API-Key"]
? headers["X-API-Key"].substring(0, 20)
: "none",
});
}
const controller = new AbortController();
const requestTimeout = options.timeout ?? this.timeout; // Use override or default
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);