phase 0 and 1
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
/**
|
||||
* Sign-in and session persistence.
|
||||
*
|
||||
* There is no token to hold. `TenantWebLogin` returns the user record and
|
||||
* nothing else, so the session IS that record. It is kept in sessionStorage
|
||||
* rather than localStorage: a shared back-office machine should not stay signed
|
||||
* in after the browser closes, and there is no server-side session to revoke.
|
||||
* The session is the user record plus, now, a signed token. Until Fiesta grew
|
||||
* `middleware.WebAuth` there was no token to hold: login returned the record and
|
||||
* nothing else, the console asserted its own `tenantid` on every request, and
|
||||
* the server believed it. The record is still what the app renders from; the
|
||||
* token is the only part the server will not take our word for.
|
||||
*
|
||||
* Kept in sessionStorage rather than localStorage: a shared back-office machine
|
||||
* should not stay signed in after the browser closes. That also means the tab
|
||||
* closing is what normally ends a session — the token's own expiry is a
|
||||
* backstop for a tab left open, not the mechanism.
|
||||
*
|
||||
* The storage key lives in `./token`, which the HTTP client also reads. It has
|
||||
* to sit under both: this file calls the API to sign in, and the client needs
|
||||
* the token to make that call authorised, so neither can import the other.
|
||||
*/
|
||||
|
||||
import { api, WEB } from '@/api/client';
|
||||
import type { FiestaUser } from '@/api/types';
|
||||
import { toSessionUser, type SessionUser } from './roles';
|
||||
|
||||
const STORAGE_KEY = 'nearle.session.v1';
|
||||
import { SESSION_STORAGE_KEY } from './token';
|
||||
|
||||
/** Thrown when the account exists but has never had a password set. */
|
||||
export class PasswordSetupRequiredError extends Error {
|
||||
@@ -72,7 +81,11 @@ export async function login(email: string, password: string): Promise<SessionUse
|
||||
throw new Error(loginMessage(envelope.code, envelope.message));
|
||||
}
|
||||
|
||||
const session = toSessionUser(envelope.details);
|
||||
// The token rides on the envelope, not on `details` — it is not a fact about
|
||||
// the user, it is what proves a later request is theirs. Absent against a
|
||||
// Fiesta that does not issue one yet, which is why it is spread in rather
|
||||
// than assigned: `exactOptionalPropertyTypes` refuses an explicit undefined.
|
||||
const session = { ...toSessionUser(envelope.details), ...(envelope.token ? { token: envelope.token } : {}) };
|
||||
persist(session);
|
||||
return session;
|
||||
}
|
||||
@@ -184,11 +197,11 @@ function loginMessage(code: number | undefined, message: string | undefined): st
|
||||
}
|
||||
|
||||
export function persist(session: SessionUser): void {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(session));
|
||||
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
|
||||
}
|
||||
|
||||
export function restore(): SessionUser | null {
|
||||
const raw = sessionStorage.getItem(STORAGE_KEY);
|
||||
const raw = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as SessionUser;
|
||||
@@ -202,5 +215,5 @@ export function restore(): SessionUser | null {
|
||||
}
|
||||
|
||||
export function clear(): void {
|
||||
sessionStorage.removeItem(STORAGE_KEY);
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user