| 1 | #!/usr/bin/env node |
| 2 | // A new VAPID key pair for browser push (services/notify). Stores nothing. |
| 3 | // |
| 4 | // node scripts/ops/vapid-keys.mjs |
| 5 | // prints both halves, base64url, for you to put where they go. |
| 6 | // |
| 7 | // node scripts/ops/vapid-keys.mjs --pipe | (cd services/notify && npx wrangler secret put VAPID_PRIVATE_KEY) |
| 8 | // writes only the private half to stdout, straight into the secret, |
| 9 | // and the public half to stderr, for VAPID_PUBLIC_KEY in |
| 10 | // services/notify/wrangler.jsonc. The private half never touches disk. |
| 11 | // |
| 12 | // A new pair invalidates every browser subscribed with the old one: they |
| 13 | // subscribe again the next time the person opens g1t with notifications on. |
| 14 | import { webcrypto } from "node:crypto"; |
| 15 | |
| 16 | const b64url = (bytes) => Buffer.from(bytes).toString("base64url"); |
| 17 | |
| 18 | const pair = await webcrypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]); |
| 19 | const publicKey = b64url(new Uint8Array(await webcrypto.subtle.exportKey("raw", pair.publicKey))); |
| 20 | const privateKey = (await webcrypto.subtle.exportKey("jwk", pair.privateKey)).d; |
| 21 | |
| 22 | if (process.argv.includes("--pipe")) { |
| 23 | process.stderr.write(`VAPID_PUBLIC_KEY=${publicKey}\n`); |
| 24 | process.stdout.write(privateKey); |
| 25 | } else { |
| 26 | console.log(`VAPID_PUBLIC_KEY=${publicKey}`); |
| 27 | console.log(`VAPID_PRIVATE_KEY=${privateKey}`); |
| 28 | console.log("\nThe private half is a secret: npx wrangler secret put VAPID_PRIVATE_KEY (in services/notify). Keep it nowhere else."); |
| 29 | } |