← back to NationalPaperHangers

scripts/gen-secrets.js

35 lines

#!/usr/bin/env node
// Generate cryptographically random values for the three signing secrets.
// Each is 48 bytes (64 base64 chars) — well past anything brute-forceable.
//
// Usage:
//   npm run gen-secrets                # print suggested .env block
//   npm run gen-secrets -- --shell     # print as `export FOO=…` lines
//   npm run gen-secrets -- --json      # print as JSON for piping to secrets-manager
//
// Note: the three secrets serve different purposes — rotating SESSION_SECRET
// invalidates active sessions, rotating BOOKING_SIGNING_SECRET breaks every
// outstanding /bookings/:uuid?t=… link in customer inboxes, rotating
// UNSUBSCRIBE_SIGNING_SECRET invalidates pending unsubscribe links. Set them
// once at first deploy and only rotate intentionally.

const crypto = require('crypto');

const KEYS = ['SESSION_SECRET', 'BOOKING_SIGNING_SECRET', 'UNSUBSCRIBE_SIGNING_SECRET'];
const ARGS = process.argv.slice(2);
const FMT = ARGS.includes('--shell') ? 'shell' : ARGS.includes('--json') ? 'json' : 'env';

function gen() { return crypto.randomBytes(48).toString('base64'); }
const out = Object.fromEntries(KEYS.map(k => [k, gen()]));

if (FMT === 'json') {
  console.log(JSON.stringify(out, null, 2));
} else if (FMT === 'shell') {
  for (const [k, v] of Object.entries(out)) console.log(`export ${k}='${v}'`);
} else {
  console.log('# Paste into .env (or route via secrets-manager):');
  for (const [k, v] of Object.entries(out)) console.log(`${k}=${v}`);
  console.log('\n# Reminder: rotating BOOKING_SIGNING_SECRET breaks outstanding booking-view links.');
  console.log('# Rotating UNSUBSCRIBE_SIGNING_SECRET breaks pending unsubscribe links. Rotate intentionally.');
}