fix(12): CR-01 guard effective-config branch during wizard in-progress
isSetupLocked() now checks for an unclaimed local wizard user (oidcIss IS NULL, claimed=false) before firing the effective-config branch. During the credential→complete window, this sentinel prevents a production container with VAPID env set from blocking POST /complete with 423. The explicit setup_complete flag (Check 1) still locks unconditionally once written. Adds regression test that sets VAPID env explicitly (no beforeEach clearing) to reproduce the production scenario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ed4e64a06a
commit
61a869ca7d
@@ -306,6 +306,21 @@ describe('POST /api/setup/config', () => {
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
// IN-01: appExternalUrl must also require https:// — it is injected as OIDC_AUTH_EXTERNAL_URL
|
||||
// (the redirect URI base) and Authelia rejects non-https redirect URIs in production.
|
||||
it('IN-01: returns 400 when appExternalUrl is an http:// URL (must require https)', async () => {
|
||||
const app = await getApp();
|
||||
const res = await app.fetch(
|
||||
jsonRequest('POST', '/api/setup/config', {
|
||||
oidcIssuer: 'https://auth.example.com',
|
||||
oidcClientId: 'familysync-client',
|
||||
vapidPublicKey: VAPID_PUBLIC_KEY,
|
||||
appExternalUrl: 'http://insecure-app.example.com',
|
||||
}),
|
||||
);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
@@ -554,4 +569,41 @@ describe('/api/setup/* — 423 when effectively configured (D-10 effective-confi
|
||||
// Not locked — no credentials means effective-config condition is false
|
||||
expect(res.status).not.toBe(423);
|
||||
});
|
||||
|
||||
// CR-01 regression: reproduces the production lock-out scenario.
|
||||
// With VAPID env PRESENT, after /credential creates an unclaimed user + credential row,
|
||||
// POST /complete must still succeed (200, writes setup_complete). Only AFTER /complete
|
||||
// runs does isSetupLocked() return true (via Check 1 / explicit flag) — so a second
|
||||
// /complete call returns 423.
|
||||
//
|
||||
// The bug was that the effective-config branch (credRow + vapidPresent) fired during
|
||||
// the credential→complete window, blocking /complete with 423 permanently.
|
||||
// beforeEach masks this by clearing VAPID env — so we set it explicitly here.
|
||||
it('CR-01: /complete succeeds when VAPID env is set AND unclaimed wizard user+credential exist (in-progress wizard)', async () => {
|
||||
// Explicitly set VAPID env (do NOT rely on beforeEach clearing it)
|
||||
process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY;
|
||||
process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY;
|
||||
|
||||
// Simulate POST /credential: creates unclaimed local user + credential (wizard in-progress)
|
||||
const userId = await seedLocalUser('cr01-regression');
|
||||
await seedCredential(userId);
|
||||
|
||||
const app = await getApp();
|
||||
|
||||
// /complete must succeed (200) — effective-config lock must NOT fire while wizard in-progress
|
||||
const completeRes = await app.fetch(jsonRequest('POST', '/api/setup/complete'));
|
||||
expect(completeRes.status).toBe(200);
|
||||
|
||||
// Verify setup_complete was written to app_config
|
||||
const [flagRow] = await db
|
||||
.select({ value: appConfig.value })
|
||||
.from(appConfig)
|
||||
.where(eq(appConfig.key, 'setup_complete'))
|
||||
.limit(1);
|
||||
expect(flagRow?.value).toBe('true');
|
||||
|
||||
// Second /complete must return 423 — explicit setup_complete flag now locks unconditionally
|
||||
const secondRes = await app.fetch(jsonRequest('POST', '/api/setup/complete'));
|
||||
expect(secondRes.status).toBe(423);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user