Adds a Super Admin ("owner") identity — SUPER_ADMIN_EMAIL, else the
bootstrap/first user (super-admin.ts) — and closes the hole where a music
account could sign into the full platform:
- Rename EXPO_PUBLIC_CLIENT_ORIGIN -> OFFICER_APP_ORIGIN.
- PUBLIC_URL + OFFICER_APP_ORIGIN are owner-only origins; MUSIC_APP_ORIGIN
stays path-scoped to /api/auth + /api/music.
- Account backstop (origin-independent): a valid non-owner token may reach
only /api/auth + /api/music regardless of Origin — airtight even if the
header is omitted/forged.
- signin rejects a non-owner logging in from an owner-only origin.
Owner keeps full access (verified); music users are confined to the music app.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
import { getUsers, getUserByEmail } from 'officerdb';
|
|
|
|
// Identifies the platform owner ("Super Admin"). Two mechanisms, both supported:
|
|
// (b) explicit — SUPER_ADMIN_EMAIL in .env designates the owner by email.
|
|
// (a) automatic — if that's unset, the bootstrap/first account (lowest user id) is the owner.
|
|
// Resolved once and cached: the owner never changes at runtime (bootstrap is closed after user #1).
|
|
const { SUPER_ADMIN_EMAIL } = process.env;
|
|
|
|
let cachedId: number | null = null;
|
|
let resolved = false;
|
|
|
|
async function resolveSuperAdminId(): Promise<number | null> {
|
|
if (resolved) return cachedId;
|
|
if (SUPER_ADMIN_EMAIL) {
|
|
const user = await getUserByEmail(SUPER_ADMIN_EMAIL);
|
|
cachedId = user?.id ?? null;
|
|
} else {
|
|
const users = await getUsers();
|
|
cachedId = users.length ? users.reduce((min, u) => (u.id < min ? u.id : min), users[0]!.id) : null;
|
|
}
|
|
resolved = true;
|
|
return cachedId;
|
|
}
|
|
|
|
export async function getSuperAdminId(): Promise<number | null> {
|
|
try {
|
|
return await resolveSuperAdminId();
|
|
} catch {
|
|
// Transient DB error before the id is cached: stay unresolved (deny) and retry next call.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function isSuperAdmin(payload: { id?: number } | null | undefined): Promise<boolean> {
|
|
if (!payload?.id) return false;
|
|
const adminId = await getSuperAdminId();
|
|
return adminId !== null && payload.id === adminId;
|
|
}
|