Files
webmail/lib/admin/admin-store.ts
T
Linus Rath 291b6364c9 feat: count admin login attempts across every instance and refuse sign-in while the shared store is down #1066
The three admin login limiters (5 per address and 50 overall in one
reservation, 5 per known device) keep their numbers and still don't count
refused attempts, but live in the store: memory in this process as before,
or the shared store with several replicas. checkRateLimit and
checkDeviceRateLimit are async now; the login route awaits them and
answers 503 "Sign-in is temporarily unavailable" when the store can't be
reached. The two limiter tests gain await and reset the store's counters,
which vi.resetModules no longer clears.

The admin code tells a store outage by the error's name, since the store
may come from another bundle's copy of the module.
2026-10-31 23:18:40 +01:00

44 lines
1.7 KiB
TypeScript

import { getStore, type CollectionOptions, type DocCollection } from '@/lib/shared-store';
import { getConfigDir, getStateDir } from './paths';
/**
* Where the admin dashboard's own documents live (#1066). Local mode: the
* admin config dir and state dir, with today's file names and temp names
* (`<file>.tmp`). Shared mode: the shared store, except that read-only
* config (ADMIN_CONFIG_READONLY) is read from the mounted files.
*/
const fixedTmpName = (target: string) => `${target}.tmp`;
/** config.json, policy.json, admin.json, .config-locked. */
export const ADMIN_CONFIG_COLLECTION = 'admin-config';
export const adminConfigOptions: CollectionOptions = {
// Owner-only: config.json carries the session secret, admin.json the password hash.
local: { dir: () => getConfigDir(), mode: 0o600, tmpName: fixedTmpName },
configDir: true,
};
/** admin-state.json, and other runtime state of the admin dashboard. */
export const ADMIN_STATE_COLLECTION = 'admin-state';
export const adminStateOptions: CollectionOptions = {
local: { dir: () => getStateDir(), tmpName: fixedTmpName },
};
export function adminConfigDocs(): DocCollection {
return getStore().collection(ADMIN_CONFIG_COLLECTION, adminConfigOptions);
}
export function adminStateDocs(): DocCollection {
return getStore().collection(ADMIN_STATE_COLLECTION, adminStateOptions);
}
/**
* Whether `error` is the shared store's StoreUnavailableError. By name, not
* instanceof: the store on globalThis may come from another bundle's copy
* of the shared-store module (proxy, instrumentation, routes), whose class
* is a different object.
*/
export function isStoreUnavailable(error: unknown): boolean {
return error instanceof Error && error.name === 'StoreUnavailableError';
}