feat: auth by default, theme toggle, CoderPad startup, review fixes
- Require a session on every route; public routes opt out with @allow_anonymous - Split password hashing and pepper loading into passwords.py - Add a system/light/dark theme toggle backed by light-dark() colors - Ignore stale 401s from an earlier session, PATCH only changed book fields, and block overlapping journal-entry saves - Add bin/start and CoderPad Vite server settings for the pad's start/restart - Rewrite README as a mise onboarding guide; expand .gitignore - Include review-round fixes and tests
This commit is contained in:
+15
-1
@@ -32,6 +32,13 @@ export class ApiError extends Error {
|
||||
}
|
||||
|
||||
let handleUnauthorized: () => void = () => {};
|
||||
let authEpoch = 0;
|
||||
|
||||
// A 401 only means "signed out" for the session that sent the request; one started before
|
||||
// a later login, logout or expiry must not end the session that replaced it.
|
||||
export function startAuthEpoch() {
|
||||
authEpoch += 1;
|
||||
}
|
||||
|
||||
export function setUnauthorizedHandler(handler: () => void) {
|
||||
handleUnauthorized = handler;
|
||||
@@ -42,6 +49,7 @@ export async function api<T>(
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
const epoch = authEpoch;
|
||||
const response = await fetch(`/api${path}`, {
|
||||
method,
|
||||
credentials: "same-origin",
|
||||
@@ -51,8 +59,14 @@ export async function api<T>(
|
||||
if (response.status === 204) return undefined as T;
|
||||
const data = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 && !path.startsWith("/auth/"))
|
||||
if (
|
||||
response.status === 401 &&
|
||||
!path.startsWith("/auth/") &&
|
||||
epoch === authEpoch
|
||||
) {
|
||||
startAuthEpoch();
|
||||
handleUnauthorized();
|
||||
}
|
||||
throw new ApiError(
|
||||
response.status,
|
||||
data?.error ?? `Request failed with status ${response.status}`,
|
||||
|
||||
Reference in New Issue
Block a user