fix: WS upgrade auth — multi-token bearer not validated
Problem: isAuthenticated() for WS upgrade only checked legacy single token. iOS bearer token (from POST /pair → createToken()) was rejected → 403 on WS. Fix: - warmTokenCache(): pre-load all multi-tokens into a sync Set on startup - validateBearerSync(): O(1) sync lookup against the cache - createToken(): adds to cache immediately on creation - isAuthenticated(): checks validateBearerSync() as third fallback
This commit is contained in:
parent
38cad794e2
commit
b64aaab40a
|
|
@ -20,6 +20,29 @@ import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// In-memory cache for sync validation (WS upgrade can't await)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const _tokenCache = new Set<string>();
|
||||||
|
|
||||||
|
function cacheToken(token: string): void {
|
||||||
|
_tokenCache.add(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sync bearer validation — checks in-memory cache populated at runtime. */
|
||||||
|
export function validateBearerSync(bearer: string): boolean {
|
||||||
|
return _tokenCache.has(bearer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Warm the cache from disk on startup. */
|
||||||
|
export async function warmTokenCache(stateDir?: string): Promise<void> {
|
||||||
|
const entries = await loadTokens(stateDir);
|
||||||
|
for (const e of entries) _tokenCache.add(e.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export interface TokenEntry {
|
export interface TokenEntry {
|
||||||
id: string;
|
id: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
|
@ -70,6 +93,7 @@ export async function createToken(
|
||||||
};
|
};
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
await saveTokens(entries, stateDir);
|
await saveTokens(entries, stateDir);
|
||||||
|
cacheToken(entry.token);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,12 @@ import {
|
||||||
generatePairingToken,
|
generatePairingToken,
|
||||||
printPairingQr,
|
printPairingQr,
|
||||||
} from "../auth/pairing.js";
|
} from "../auth/pairing.js";
|
||||||
import { createToken, validateBearer } from "../auth/tokens.js";
|
import {
|
||||||
|
createToken,
|
||||||
|
validateBearer,
|
||||||
|
validateBearerSync,
|
||||||
|
warmTokenCache,
|
||||||
|
} from "../auth/tokens.js";
|
||||||
import {
|
import {
|
||||||
generateSessionId,
|
generateSessionId,
|
||||||
loadOrCreateToken,
|
loadOrCreateToken,
|
||||||
|
|
@ -81,6 +86,7 @@ export async function startServer(
|
||||||
const clientChangeListeners: Array<() => void> = [];
|
const clientChangeListeners: Array<() => void> = [];
|
||||||
const clients = new Set<WsClient>();
|
const clients = new Set<WsClient>();
|
||||||
const token = await loadOrCreateToken();
|
const token = await loadOrCreateToken();
|
||||||
|
await warmTokenCache(); // pre-load multi-tokens into sync cache
|
||||||
|
|
||||||
// Map of valid session IDs → expiry timestamp (ms since epoch)
|
// Map of valid session IDs → expiry timestamp (ms since epoch)
|
||||||
const SESSION_TTL_MS = 86_400_000; // 24 h — matches cookie Max-Age
|
const SESSION_TTL_MS = 86_400_000; // 24 h — matches cookie Max-Age
|
||||||
|
|
@ -112,8 +118,7 @@ export async function startServer(
|
||||||
const bearer = extractBearer(req);
|
const bearer = extractBearer(req);
|
||||||
if (bearer) {
|
if (bearer) {
|
||||||
if (validateToken(bearer, token)) return true;
|
if (validateToken(bearer, token)) return true;
|
||||||
// async validateBearer checked in asyncHandler for API routes;
|
if (validateBearerSync(bearer)) return true; // multi-token sync cache
|
||||||
// for WS upgrade we do a sync fallback: legacy token only here.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue