Phase 12: Initial Setup Wizard #22
@@ -193,10 +193,17 @@ setupRouter.post('/validate/oidc', async (c) => {
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST /api/setup/validate/vapid
|
||||
//
|
||||
// Validates VAPID key pair structure by calling webpush.setVapidDetails().
|
||||
// Reads BOTH keys ONLY from process.env — NEVER from app_config (T-12-06 / D-01 / SC-3).
|
||||
// VAPID_PRIVATE_KEY is never returned in any response.
|
||||
// Returns 200 { ok: true } on structural validity, 400 { ok: false } on failure.
|
||||
// Validates the operator-entered VAPID public key (app_config.vapid_public_key)
|
||||
// against the configured key pair:
|
||||
// 1. Equality (gap 2): the submitted public key MUST equal process.env.VAPID_PUBLIC_KEY.
|
||||
// A wrong/typoed key (e.g. "BH123") now fails the row and gates Continue — push
|
||||
// would silently break in production otherwise (SETUP-02).
|
||||
// 2. Structural: webpush.setVapidDetails() validates the env pair's byte structure.
|
||||
//
|
||||
// VAPID_PRIVATE_KEY is read ONLY from process.env — NEVER from app_config or returned
|
||||
// (T-12-06 / D-01 / SC-3). The equality check compares the submitted PUBLIC key to the
|
||||
// env PUBLIC key only — the private key is never compared or echoed.
|
||||
// Returns 200 { ok: true } on success, 400 { ok: false } on any failure.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
setupRouter.post('/validate/vapid', async (c) => {
|
||||
@@ -210,6 +217,23 @@ setupRouter.post('/validate/vapid', async (c) => {
|
||||
return c.json({ ok: false, error: 'VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY env vars must be set' }, 400);
|
||||
}
|
||||
|
||||
// Gap 2: assert the operator-submitted public key matches the env public key BEFORE
|
||||
// the structural check. Read the submitted key from app_config (same idiom as validate/oidc).
|
||||
const [submittedRow] = await db
|
||||
.select({ value: appConfig.value })
|
||||
.from(appConfig)
|
||||
.where(eq(appConfig.key, 'vapid_public_key'))
|
||||
.limit(1);
|
||||
|
||||
const submittedPublicKey = submittedRow?.value;
|
||||
if (!submittedPublicKey || submittedPublicKey !== publicKey) {
|
||||
return c.json({
|
||||
ok: false,
|
||||
error:
|
||||
'VAPID public key does not match the configured key pair. Paste the exact VAPID_PUBLIC_KEY printed by `npm run generate-secrets`.',
|
||||
}, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
// setVapidDetails runs validatePrivateKey (32-byte check) and validatePublicKey (65-byte check)
|
||||
// internally — this is the library's own structural validation (Pattern 7).
|
||||
|
||||
Reference in New Issue
Block a user