From 2d6dc14a4c8f8895610af619ec5e032bb0e63264 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Mon, 15 Jun 2026 13:39:32 -0400 Subject: [PATCH] =?UTF-8?q?feat(12-01):=20generate-secrets=20helper=20?= =?UTF-8?q?=E2=80=94=20SETUP-03=20/=20D-05?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add scripts/generate-secrets.mjs: plain ESM script that prints SESSION_SECRET + APP_PASSWORD_ENCRYPTION_KEY (32 random bytes each, hex-encoded) and VAPID_PUBLIC_KEY + VAPID_PRIVATE_KEY from web-push generateVAPIDKeys() — all to stdout only (SC-3: nothing written to disk) - Resolve web-push as CommonJS default import from apps/api/node_modules (avoids a root-level dependency; named-export ESM form not supported) - Wire root package.json "generate-secrets" script: node scripts/generate-secrets.mjs --- package.json | 3 ++- scripts/generate-secrets.mjs | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 scripts/generate-secrets.mjs diff --git a/package.json b/package.json index 38eabbb..692dc0a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "typecheck": "pnpm -r typecheck", "format": "prettier --write .", "format:check": "prettier --check .", - "md:lint": "markdownlint-cli2" + "md:lint": "markdownlint-cli2", + "generate-secrets": "node scripts/generate-secrets.mjs" }, "devDependencies": { "@eslint/js": "9.39.4", diff --git a/scripts/generate-secrets.mjs b/scripts/generate-secrets.mjs new file mode 100644 index 0000000..ca83954 --- /dev/null +++ b/scripts/generate-secrets.mjs @@ -0,0 +1,40 @@ +/** + * generate-secrets.mjs — FamilySync bootstrap secret generator (SETUP-03 / D-05). + * + * Generates all secrets required for a first-time FamilySync deployment: + * - SESSION_SECRET (AES-256-GCM session signing key, 32 random bytes / 64 hex chars) + * - APP_PASSWORD_ENCRYPTION_KEY (AES-256-GCM encryption key, 32 random bytes / 64 hex chars) + * - VAPID_PUBLIC_KEY (EC P-256 public key, base64url, ~87 chars) + * - VAPID_PRIVATE_KEY (EC P-256 private scalar, base64url, ~43 chars) + * + * Security contract (SC-3): + * - Prints to stdout ONLY — never writes any file, never touches the DB, never calls any API. + * - The operator is responsible for pasting the output into docker-compose.yml and keeping it safe. + * - These values CANNOT be recovered if lost (VAPID key rotation invalidates push subscriptions). + * + * Usage: + * node scripts/generate-secrets.mjs + * # or via pnpm script: + * pnpm generate-secrets + */ + +// web-push is a CommonJS module — import via default then destructure. +// Resolve from apps/api/node_modules to avoid a root-level dependency. +import webpush from '../apps/api/node_modules/web-push/src/index.js'; +const { generateVAPIDKeys } = webpush; +import { randomBytes } from 'node:crypto'; + +const sessionSecret = randomBytes(32).toString('hex'); +const encKey = randomBytes(32).toString('hex'); +const vapid = generateVAPIDKeys(); + +console.log(`# FamilySync Bootstrap Secrets — generated ${new Date().toISOString()} +# Paste into your docker-compose.yml environment block under the 'api' service. +# Keep this output safe — these values cannot be recovered if lost. +# VAPID key rotation will invalidate all existing push subscriptions. + +SESSION_SECRET=${sessionSecret} +APP_PASSWORD_ENCRYPTION_KEY=${encKey} +VAPID_PUBLIC_KEY=${vapid.publicKey} +VAPID_PRIVATE_KEY=${vapid.privateKey} +`);