fix(api): close second silent dev-login path in authedRequest
Oracle round-4 finding: authedRequest() called the private login() helper whenever no bearer token existed AND on 401 retry. That helper posts to /api/v1/auth/dev-login, so any authenticated API call could silently issue dev-login regardless of UI gating. - authedRequest checks devLoginAllowed() (VITE_ENABLE_DEV_LOGIN !== 'false') before calling login(). When dev-login is disabled it throws AuthRequiredError instead. - 401 retry path gated the same way. - New AuthRequiredError exported so callers (store, scenes) can route unauthenticated users to the login page instead of swallowing. - src/lib/api.test.ts: regression test 'throws AuthRequiredError instead of silently calling /dev-login when no token'. With VITE_ENABLE_DEV_LOGIN=false api.me() rejects with /Sign in required/ and no POST to /dev-login is observed. 30/30 unit tests green.
This commit is contained in:
+13
-1
@@ -138,6 +138,14 @@ async function instrumentedFetch(
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthRequiredError extends Error {
|
||||
constructor(msg = "Sign in required") { super(msg); this.name = "AuthRequiredError"; }
|
||||
}
|
||||
|
||||
function devLoginAllowed(): boolean {
|
||||
return (import.meta.env?.VITE_ENABLE_DEV_LOGIN ?? "true") !== "false";
|
||||
}
|
||||
|
||||
async function authedRequest<T>(
|
||||
cfg: ApiConfig,
|
||||
method: string,
|
||||
@@ -146,7 +154,10 @@ async function authedRequest<T>(
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
let token = sessionStorage.getItem(TOKEN_KEY);
|
||||
if (!token) token = await login(cfg, signal);
|
||||
if (!token) {
|
||||
if (!devLoginAllowed()) throw new AuthRequiredError();
|
||||
token = await login(cfg, signal);
|
||||
}
|
||||
const init: RequestInit = {
|
||||
method,
|
||||
headers: {
|
||||
@@ -160,6 +171,7 @@ async function authedRequest<T>(
|
||||
let r = await instrumentedFetch(method, `${cfg.baseUrl}${path}`, init, body);
|
||||
if (r.status === 401) {
|
||||
sessionStorage.removeItem(TOKEN_KEY);
|
||||
if (!devLoginAllowed()) throw new AuthRequiredError();
|
||||
token = await login(cfg, signal);
|
||||
init.headers = { ...init.headers, Authorization: `Bearer ${token}` };
|
||||
r = await instrumentedFetch(method, `${cfg.baseUrl}${path}`, init, body);
|
||||
|
||||
Reference in New Issue
Block a user