fix(auth): make diagnostics match runtime safety

This commit is contained in:
2026-08-17 11:23:52 +02:00
parent f0ae680671
commit 7cfbee36fa
14 changed files with 619 additions and 99 deletions
+13 -5
View File
@@ -32,6 +32,8 @@ export interface LocalUserRecord {
}
export interface LocalUserRegistry {
/** Safe production diagnostic probe; never returns user records or hashes. */
hasEnabledAdmin(): Promise<boolean>;
findByUsername(username: string): Promise<LocalUserRecord | undefined>;
findBySubject(id: string): Promise<LocalUserRecord | undefined>;
verify(user: LocalUserRecord | undefined, password: string): Promise<boolean>;
@@ -191,13 +193,11 @@ function parseRegistry(source: string): LocalUserRecord[] {
const parsed = registrySchema.parse(document.toJSON());
const ids = new Set<string>();
const usernames = new Set<string>();
let enabledAdmin = false;
const records = parsed.users.map((user) => {
const normalizedUsername = normalizeUsername(user.username);
if (ids.has(user.id) || usernames.has(normalizedUsername)) throw invalid();
ids.add(user.id);
usernames.add(normalizedUsername);
if (user.enabled && user.roles.includes("admin")) enabledAdmin = true;
return Object.freeze({
id: user.id,
username: user.username,
@@ -209,7 +209,6 @@ function parseRegistry(source: string): LocalUserRecord[] {
authRevision: user.authRevision,
});
});
if (!enabledAdmin) throw invalid();
return records;
} catch {
throw invalid();
@@ -241,13 +240,22 @@ export function createLocalUserRegistry(usersPath: string): LocalUserRegistry {
throw invalid();
}
function operationalRecords(): LocalUserRecord[] {
const records = current();
if (!records.some((user) => user.enabled && user.roles.includes("admin"))) throw invalid();
return records;
}
return {
async hasEnabledAdmin(): Promise<boolean> {
return current().some((user) => user.enabled && user.roles.includes("admin"));
},
async findByUsername(username: string): Promise<LocalUserRecord | undefined> {
const normalized = normalizeUsername(username);
return current().find((user) => user.normalizedUsername === normalized);
return operationalRecords().find((user) => user.normalizedUsername === normalized);
},
async findBySubject(id: string): Promise<LocalUserRecord | undefined> {
return current().find((user) => user.id === id);
return operationalRecords().find((user) => user.id === id);
},
async verify(user: LocalUserRecord | undefined, password: string): Promise<boolean> {
if (!user || !user.enabled) {