Phase 12: Initial Setup Wizard #22

Merged
luckberg merged 76 commits from gsd/phase-12-initial-setup-wizard into main 2026-06-16 19:10:33 -04:00
Showing only changes of commit e9d07b38fb - Show all commits
+43
View File
@@ -345,6 +345,12 @@ describe('POST /api/setup/validate/vapid', () => {
it('returns 200 { ok: true } for a valid VAPID key pair from generate-secrets', async () => { it('returns 200 { ok: true } for a valid VAPID key pair from generate-secrets', async () => {
process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY; process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY;
process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY; process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY;
// Gap 2: the operator-submitted public key (app_config.vapid_public_key) must equal
// process.env.VAPID_PUBLIC_KEY for the happy path. Seed the matching row.
await db
.insert(appConfig)
.values({ key: 'vapid_public_key', value: VAPID_PUBLIC_KEY })
.onDuplicateKeyUpdate({ set: { value: VAPID_PUBLIC_KEY } });
const app = await getApp(); const app = await getApp();
const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid')); const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid'));
@@ -353,6 +359,43 @@ describe('POST /api/setup/validate/vapid', () => {
expect(body.ok).toBe(true); expect(body.ok).toBe(true);
}); });
// Gap 2 (major): a wrong wizard-entered public key (e.g. "BH123") must fail the row.
// The env VAPID pair is valid, but the submitted key in app_config does not match
// process.env.VAPID_PUBLIC_KEY → 400. The response must NEVER contain VAPID_PRIVATE_KEY (T-12-06).
it('returns 400 when submitted vapid_public_key does not match process.env.VAPID_PUBLIC_KEY (gap 2)', async () => {
process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY;
process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY;
// Operator typed a clearly-wrong key — env pair is still structurally valid.
await db
.insert(appConfig)
.values({ key: 'vapid_public_key', value: 'BH123' })
.onDuplicateKeyUpdate({ set: { value: 'BH123' } });
const app = await getApp();
const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid'));
expect(res.status).toBe(400);
const bodyText = await res.text();
const body = JSON.parse(bodyText) as { ok: boolean };
expect(body.ok).toBe(false);
// T-12-06: VAPID_PRIVATE_KEY must never leak into any response.
expect(bodyText).not.toContain(VAPID_PRIVATE_KEY);
});
// Gap 2: without an operator-submitted key there is nothing to compare → 400.
it('returns 400 when app_config.vapid_public_key row is absent (no submitted key)', async () => {
process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY;
process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY;
// No vapid_public_key seeded in app_config (cleaned in afterEach).
const app = await getApp();
const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid'));
expect(res.status).toBe(400);
const bodyText = await res.text();
const body = JSON.parse(bodyText) as { ok: boolean };
expect(body.ok).toBe(false);
expect(bodyText).not.toContain(VAPID_PRIVATE_KEY);
});
it('returns 400 { ok: false } when VAPID env vars are missing', async () => { it('returns 400 { ok: false } when VAPID env vars are missing', async () => {
// VAPID env vars not set (cleared in beforeEach) // VAPID env vars not set (cleared in beforeEach)
const app = await getApp(); const app = await getApp();