pediatric-ai-scribe-v3/public/js/authFetch.js
Daniel 4c699b86ef feat: local registration is closed for good, and its on/off switch is gone
Every account comes through One Sign In; an administrator sends an
invitation link from there. The register route answers 410, the
registration-status route and the admin toggle are removed, and the
setting no longer exists in defaults, lockdown lists or seeds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fZGJNyDvERbMgS2Uc2msP
2026-09-15 03:18:57 +02:00

92 lines
4.5 KiB
JavaScript

// Account-bound requests cannot outlive the document's verified owner.
if (!window.__fetchAuthIntercepted) {
window.__fetchAuthIntercepted = true;
var rawFetch = window.fetch.bind(window);
var boundary = window.AccountBoundary;
// Callable with no verified owner, because they are how one is obtained.
// Anything not on this list is rejected before it is sent, which is right —
// but it means a new sign-in endpoint has to be added here or it fails as a
// "Connection error" with no request ever leaving the browser.
var authPaths = new Set([
'/api/auth/login', '/api/auth/register', '/api/auth/logout',
'/api/auth/forgot-password', '/api/auth/reset-password',
'/api/auth/verify-email', '/api/auth/resend-verification',
'/api/auth/oidc', '/api/auth/oidc-status'
]);
boundary.logoutRequest = function(headers) {
return rawFetch('/api/auth/logout', {
method: 'POST', headers: headers, credentials: 'same-origin', keepalive: true
});
};
boundary.cookieSessionRequest = function() {
return rawFetch('/api/auth/me', { credentials: 'same-origin', cache: 'no-store' });
};
if (navigator.sendBeacon) {
var sendBeacon = navigator.sendBeacon.bind(navigator);
navigator.sendBeacon = function(input, data) {
var url = new URL(input, window.location.href);
if (url.origin === window.location.origin && url.pathname.startsWith('/api/') && !boundary.active()) return false;
return sendBeacon(input, data);
};
}
window.fetch = function(input, init) {
var url;
try { url = new URL(typeof input === 'string' || input instanceof URL ? input : input.url, window.location.href); }
catch (e) { return rawFetch(input, init); }
if (url.origin !== window.location.origin || !url.pathname.startsWith('/api/')) return rawFetch(input, init);
if (boundary.blocked()) return Promise.reject(boundary.error());
var ticket = boundary.capture(); // Also detects shared-cookie changes before storage events arrive.
if (boundary.blocked()) return Promise.reject(boundary.error());
// No clinical module may preload a previous cookie's data on the login screen.
if (!ticket && !authPaths.has(url.pathname) && url.pathname !== '/api/auth/me' && url.pathname !== '/api/models') {
return Promise.reject(boundary.error());
}
var login = (url.pathname === '/api/auth/login' || url.pathname === '/api/auth/register')
&& String((init && init.method) || (input && input.method) || 'GET').toUpperCase() === 'POST';
try { if (login) boundary.startLogin(); } catch (e) { return Promise.reject(e); }
var revision = boundary.revision();
var shared = boundary.read();
var generation = shared && shared.generation;
var abort = new AbortController();
var callerSignal = (init && init.signal) || (input && input.signal);
var accountSignal = boundary.signal();
function cancel() { abort.abort(); }
if (callerSignal) {
if (callerSignal.aborted) cancel();
else callerSignal.addEventListener('abort', cancel, { once: true });
}
if (!login) accountSignal.addEventListener('abort', cancel, { once: true });
function check() {
var now = boundary.read();
if (revision !== boundary.revision() || generation !== (now && now.generation)
|| (!login && (boundary.blocked() || (ticket && !boundary.valid(ticket))))) throw boundary.error();
}
function guardResponse(resp) {
check();
if (resp.status === 401 && ticket && !authPaths.has(url.pathname)) {
boundary.end();
boundary.reload();
throw boundary.error();
}
// Body parsing can finish after fetch itself; gate those continuations too.
['json', 'text', 'blob', 'arrayBuffer', 'formData'].forEach(function(method) {
if (!resp[method]) return;
var original = resp[method].bind(resp);
resp[method] = function() { check(); return original().then(function(value) { check(); return value; }); };
});
if (resp.clone) {
var clone = resp.clone.bind(resp);
resp.clone = function() { check(); return guardResponse(clone()); };
}
return resp;
}
return rawFetch(input, Object.assign({}, init, { signal: abort.signal }))
.then(guardResponse).finally(function() {
if (callerSignal) callerSignal.removeEventListener('abort', cancel);
// Keep the account abort listener until document disposal: it also
// cancels a response body that has not finished streaming yet.
});
};
}