feat(security): implement API-verified passcode auth with JWT session
Passcodes are no longer compared locally against cached localStorage data. Entry now POSTs to /v3/action/auth/authenticate_passcode; on success the returned JWT (with per-role TTL) is stored in $ae_loc.jwt. Page-load expiry check in +layout.ts resets access_type to anonymous when the JWT has expired, targeting only auth_type='passcode' JWTs. - Debounce (600 ms) auto-fires the check after typing stops; Enter key fires immediately as a secondary trigger — preserving the original UX - Inline spinner and error message added to the passcode input - Silent fallback to local comparison on network error or unresolved site_id (ghost), so IDAA staff and Electron/Launcher contexts are safe - USE_API_PASSCODE_AUTH = true (active); local fallback retained while production is observed; site_access_code_kv cleanup deferred Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,36 @@ export async function load({ fetch, params, parent, route, url }) {
|
||||
if (ae_loc_json.jwt) {
|
||||
ae_api_init['jwt'] = ae_loc_json.jwt;
|
||||
}
|
||||
|
||||
// Enforce passcode JWT TTL on every page load.
|
||||
// Decodes the JWT payload (base64, no secret needed) and resets the
|
||||
// session to anonymous if the passcode JWT has expired.
|
||||
// User login JWTs (auth_type !== 'passcode') are never touched here.
|
||||
// This block is always active but only fires once USE_API_PASSCODE_AUTH
|
||||
// has been flipped and the user has a passcode JWT in their ae_loc.
|
||||
if (ae_loc_json?.jwt) {
|
||||
try {
|
||||
const parts = ae_loc_json.jwt.split('.');
|
||||
if (parts.length === 3) {
|
||||
const jwt_payload = JSON.parse(atob(parts[1]));
|
||||
const json_str =
|
||||
typeof jwt_payload.json_str === 'string'
|
||||
? JSON.parse(jwt_payload.json_str)
|
||||
: jwt_payload.json_str;
|
||||
if (
|
||||
json_str?.auth_type === 'passcode' &&
|
||||
jwt_payload.eat < Date.now() / 1000
|
||||
) {
|
||||
// Passcode session has expired — drop to anonymous.
|
||||
ae_loc_json.jwt = null;
|
||||
ae_loc_json.access_type = 'anonymous';
|
||||
localStorage.setItem('ae_loc', JSON.stringify(ae_loc_json));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Malformed JWT — leave untouched, let existing handling deal with it.
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail on storage read
|
||||
|
||||
Reference in New Issue
Block a user