fix(auth): bind current local registry and CORS

This commit is contained in:
2026-08-17 00:34:58 +02:00
parent c34e01e9b9
commit 94c2cd3709
15 changed files with 514 additions and 87 deletions
+66 -3
View File
@@ -27,7 +27,7 @@ function localConfig(url = publicUrl) {
}
function usersYaml(options: { enabled?: boolean; username?: string } = {}): string {
return [
const users = [
"version: 1",
"users:",
` - id: ${adminId}`,
@@ -38,8 +38,19 @@ function usersYaml(options: { enabled?: boolean; username?: string } = {}): stri
" - admin",
` enabled: ${options.enabled ?? true}`,
" authRevision: 1",
"",
].join("\n");
];
if (options.enabled === false) {
users.push(
" - id: 6ba7b811-9dad-4ed1-80b4-00c04fd430c8",
" username: BackupAdmin",
` passwordHash: ${passwordHash}`,
" roles:",
" - admin",
" enabled: true",
" authRevision: 1",
);
}
return [...users, ""].join("\n");
}
function firstSetCookie(response: { headers: Record<string, string | string[] | undefined> }): string {
@@ -401,6 +412,58 @@ test("a verifier failure is sanitized and releases its concurrency permit", asyn
expect(attempts).toBe(2);
});
test("operational registry failures do dummy work, return 503, and never consume login-failure capacity", async () => {
let available = false;
let verificationCalls = 0;
const user = {
id: adminId, username: "Admin", normalizedUsername: "admin", displayName: "Local administrator",
passwordHash, roles: ["admin"], enabled: true, authRevision: 1,
};
const { app } = await createLocalApp({
registry: {
findByUsername: async () => {
if (!available) throw new Error("registry file is unavailable");
return user;
},
findBySubject: async () => user,
verify: async () => {
verificationCalls += 1;
return false;
},
},
});
for (let attempt = 0; attempt < 11; attempt += 1) {
const response = await login(app);
expect(response.statusCode).toBe(503);
expect(response.json()).toEqual({ code: "auth_unavailable", error: "Authentication is unavailable" });
}
expect(verificationCalls).toBe(0);
available = true;
for (let attempt = 0; attempt < 10; attempt += 1) expect((await login(app)).statusCode).toBe(401);
expect((await login(app)).statusCode).toBe(429);
});
test("operational config failures return 503 and never consume login-failure capacity", async () => {
const { app, authConfigFile } = await createLocalApp();
writeFileSync(authConfigFile, "version: 1\nmode: unsupported\n", { encoding: "utf8", mode: 0o600 });
chmodSync(authConfigFile, 0o600);
for (let attempt = 0; attempt < 11; attempt += 1) {
const response = await login(app);
expect(response.statusCode).toBe(503);
expect(response.json()).toEqual({ code: "auth_unavailable", error: "Authentication is unavailable" });
}
writeFileSync(authConfigFile, stringify(localConfig()), { encoding: "utf8", mode: 0o600 });
chmodSync(authConfigFile, 0o600);
for (let attempt = 0; attempt < 10; attempt += 1) {
expect((await login(app, { password: `${password}!` })).statusCode).toBe(401);
}
expect((await login(app, { password: `${password}!` })).statusCode).toBe(429);
});
test("public auth configuration is safe and OIDC protocol placeholders fail closed", async () => {
const { app } = await createLocalApp();
const configuration = await app.inject({ method: "GET", url: "/auth/config" });