CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

feat(email): Google Workspace domain import (Phase 1) + Business pricing #4071

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/google-oauth-setup-UYn0Rmainopened Jun 11, 2026
1 changed file+37−28
Modifiedapps/api/src/lib/jwt.ts+37−28View fileUnifiedSplit
2424
2525 const privPem = process.env["JWT_PRIVATE_KEY"];
2626 const pubPem = process.env["JWT_PUBLIC_KEY"];
27 const isProd = process.env["NODE_ENV"] === "production";
2728
29 // 1. Stable RS256 from a provided key pair (best for multi-node verification).
2830 if (privPem && pubPem) {
2931 try {
3032 privateKey = await jose.importPKCS8(privPem, "RS256");
3335 console.log("[jwt] Using RS256 with provided key pair");
3436 return;
3537 } catch (err) {
36 console.warn("[jwt] Failed to import RS256 keys, will attempt auto-generation:", err);
38 console.warn("[jwt] Failed to import RS256 keys, trying JWT_SECRET:", err);
3739 }
3840 }
3941
40 // Attempt to generate RSA key pair at runtime if not provided
41 if (!privPem && !pubPem) {
42 try {
43 const { privateKey: genPriv, publicKey: genPub } = await jose.generateKeyPair("RS256", {
44 modulusLength: 2048,
45 });
46 privateKey = genPriv;
47 publicKey = genPub;
48 algorithm = "RS256";
49 console.log("[jwt] Generated ephemeral RS256 key pair (set JWT_PRIVATE_KEY / JWT_PUBLIC_KEY for persistence)");
50 return;
51 } catch {
52 // WebCrypto RSA generation may not be available in all runtimes
53 console.warn("[jwt] RS256 key generation unavailable, falling back to HS256");
42 // 2. Stable HS256 from JWT_SECRET. Preferred over an ephemeral RS256 key pair
43 // because ephemeral keys are regenerated on every boot, which silently
44 // invalidates every active session on restart ("invalid or expired bearer
45 // token" after a deploy). A configured JWT_SECRET must actually take effect.
46 const explicitSecret = process.env["JWT_SECRET"];
47 if (explicitSecret) {
48 if (explicitSecret.length < 32 && isProd) {
49 throw new Error("[jwt] JWT_SECRET must be at least 32 characters in production.");
5450 }
51 const encoded = new TextEncoder().encode(explicitSecret);
52 privateKey = encoded;
53 publicKey = encoded;
54 algorithm = "HS256";
55 console.log("[jwt] Using HS256 with JWT_SECRET (stable across restarts)");
56 return;
5557 }
5658
57 // HS256 fallback
58 const explicitSecret = process.env["JWT_SECRET"];
59 if (!explicitSecret && process.env["NODE_ENV"] === "production") {
59 // 3. No stable signing material provided. Refuse in production — an ephemeral
60 // key pair would log every user out on every restart.
61 if (isProd) {
6062 throw new Error(
6163 "[jwt] Refusing to start in production without JWT_PRIVATE_KEY + JWT_PUBLIC_KEY or JWT_SECRET. " +
62 "Set one of these env vars before starting the API.",
64 "Set one of these so sessions survive restarts.",
6365 );
6466 }
65 if (explicitSecret && explicitSecret.length < 32 && process.env["NODE_ENV"] === "production") {
66 throw new Error("[jwt] JWT_SECRET must be at least 32 characters in production.");
67 }
68 const secret = explicitSecret ?? "dev_secret";
69 if (secret === "dev_secret") {
70 console.warn("[jwt] WARNING: Using default HS256 secret. Set JWT_PRIVATE_KEY + JWT_PUBLIC_KEY for RS256 in production.");
67
68 // 4. Dev only: ephemeral RS256, or a dev secret as a last resort.
69 try {
70 const { privateKey: genPriv, publicKey: genPub } = await jose.generateKeyPair("RS256", {
71 modulusLength: 2048,
72 });
73 privateKey = genPriv;
74 publicKey = genPub;
75 algorithm = "RS256";
76 console.log("[jwt] Generated ephemeral RS256 key pair (dev only — set JWT_SECRET to persist sessions)");
77 } catch {
78 const encoded = new TextEncoder().encode("dev_secret");
79 privateKey = encoded;
80 publicKey = encoded;
81 algorithm = "HS256";
82 console.warn("[jwt] WARNING: using default dev HS256 secret.");
7183 }
72 privateKey = new TextEncoder().encode(secret);
73 publicKey = new TextEncoder().encode(secret);
74 algorithm = "HS256";
7584}
7685
7786// ─── Crypto helpers ──────────────────────────────────────────────────────────
7887
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts