From b53e5d4430ab5027c0b7a3fb4086c0229a69cf7a Mon Sep 17 00:00:00 2001 From: Mark McIntosh Date: Fri, 20 Feb 2026 08:54:49 -0500 Subject: [PATCH] security: move JWT secret to environment variable - Add optional secret parameter to generateToken/verifyToken - Falls back to hardcoded constant for local dev without wrangler secret - Add JWT_SECRET to Bindings interface - Update all generateToken callsites to pass c.env.JWT_SECRET - Update requireAuth and optionalAuth middleware to pass env secret - Update magic-link-auth and otp-login plugins Production: set via `wrangler secret put JWT_SECRET` Fixes VULN-001 --- packages/core/src/app.ts | 1 + packages/core/src/middleware/auth.ts | 20 ++++++++++--------- .../available/magic-link-auth/index.ts | 3 ++- .../core-plugins/otp-login-plugin/index.ts | 2 +- packages/core/src/routes/auth.ts | 16 +++++++-------- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/core/src/app.ts b/packages/core/src/app.ts index 3865b457c..0ef3ca1cf 100644 --- a/packages/core/src/app.ts +++ b/packages/core/src/app.ts @@ -53,6 +53,7 @@ export interface Bindings { IMAGES_ACCOUNT_ID?: string IMAGES_API_TOKEN?: string ENVIRONMENT?: string + JWT_SECRET?: string BUCKET_NAME?: string GOOGLE_MAPS_API_KEY?: string } diff --git a/packages/core/src/middleware/auth.ts b/packages/core/src/middleware/auth.ts index 29c501ed4..243470d9e 100644 --- a/packages/core/src/middleware/auth.ts +++ b/packages/core/src/middleware/auth.ts @@ -10,11 +10,11 @@ type JWTPayload = { iat: number } -// JWT secret - in production this should come from environment variables -const JWT_SECRET = 'your-super-secret-jwt-key-change-in-production' +// Fallback JWT secret for local development only (no wrangler secret set) +const JWT_SECRET_FALLBACK = 'your-super-secret-jwt-key-change-in-production' export class AuthManager { - static async generateToken(userId: string, email: string, role: string): Promise { + static async generateToken(userId: string, email: string, role: string, secret?: string): Promise { const payload: JWTPayload = { userId, email, @@ -22,13 +22,13 @@ export class AuthManager { exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24), // 24 hours iat: Math.floor(Date.now() / 1000) } - - return await sign(payload, JWT_SECRET, 'HS256') + + return await sign(payload, secret || JWT_SECRET_FALLBACK, 'HS256') } - static async verifyToken(token: string): Promise { + static async verifyToken(token: string, secret?: string): Promise { try { - const payload = await verify(token, JWT_SECRET, 'HS256') as JWTPayload + const payload = await verify(token, secret || JWT_SECRET_FALLBACK, 'HS256') as JWTPayload // Check if token is expired if (payload.exp < Math.floor(Date.now() / 1000)) { @@ -112,7 +112,8 @@ export const requireAuth = () => { // If not cached, verify token if (!payload) { - payload = await AuthManager.verifyToken(token) + const jwtSecret = (c.env as any)?.JWT_SECRET + payload = await AuthManager.verifyToken(token, jwtSecret) // Cache the verified payload for 5 minutes if (payload && kv) { @@ -186,7 +187,8 @@ export const optionalAuth = () => { } if (token) { - const payload = await AuthManager.verifyToken(token) + const jwtSecret = (c.env as any)?.JWT_SECRET + const payload = await AuthManager.verifyToken(token, jwtSecret) if (payload) { c.set('user', payload) } diff --git a/packages/core/src/plugins/available/magic-link-auth/index.ts b/packages/core/src/plugins/available/magic-link-auth/index.ts index 43efecdeb..906dd6332 100644 --- a/packages/core/src/plugins/available/magic-link-auth/index.ts +++ b/packages/core/src/plugins/available/magic-link-auth/index.ts @@ -204,7 +204,8 @@ export function createMagicLinkAuthPlugin(): Plugin { const jwtToken = await AuthManager.generateToken( user.id, user.email, - user.role + user.role, + (c.env as any).JWT_SECRET ) // Set auth cookie diff --git a/packages/core/src/plugins/core-plugins/otp-login-plugin/index.ts b/packages/core/src/plugins/core-plugins/otp-login-plugin/index.ts index b394c96a2..ac1d90007 100644 --- a/packages/core/src/plugins/core-plugins/otp-login-plugin/index.ts +++ b/packages/core/src/plugins/core-plugins/otp-login-plugin/index.ts @@ -282,7 +282,7 @@ export function createOTPLoginPlugin(): Plugin { } // Generate JWT token - const token = await AuthManager.generateToken(user.id, user.email, user.role) + const token = await AuthManager.generateToken(user.id, user.email, user.role, (c.env as any).JWT_SECRET) // Set HTTP-only cookie setCookie(c, 'auth_token', token, { diff --git a/packages/core/src/routes/auth.ts b/packages/core/src/routes/auth.ts index c36513ff2..06aa2ca90 100644 --- a/packages/core/src/routes/auth.ts +++ b/packages/core/src/routes/auth.ts @@ -150,7 +150,7 @@ authRoutes.post('/register', ).run() // Generate JWT token - const token = await AuthManager.generateToken(userId, normalizedEmail, 'viewer') + const token = await AuthManager.generateToken(userId, normalizedEmail, 'viewer', c.env.JWT_SECRET) // Set HTTP-only cookie setCookie(c, 'auth_token', token, { @@ -226,8 +226,8 @@ authRoutes.post('/login', async (c) => { } // Generate JWT token - const token = await AuthManager.generateToken(user.id, user.email, user.role) - + const token = await AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET) + // Set HTTP-only cookie setCookie(c, 'auth_token', token, { httpOnly: true, @@ -323,7 +323,7 @@ authRoutes.post('/refresh', requireAuth(), async (c) => { } // Generate new token - const token = await AuthManager.generateToken(user.userId, user.email, user.role) + const token = await AuthManager.generateToken(user.userId, user.email, user.role, c.env.JWT_SECRET) // Set new cookie setCookie(c, 'auth_token', token, { @@ -436,7 +436,7 @@ authRoutes.post('/register/form', async (c) => { ).run() // Generate JWT token - const token = await AuthManager.generateToken(userId, normalizedEmail, role) + const token = await AuthManager.generateToken(userId, normalizedEmail, role, c.env.JWT_SECRET) // Set HTTP-only cookie setCookie(c, 'auth_token', token, { @@ -516,8 +516,8 @@ authRoutes.post('/login/form', async (c) => { } // Generate JWT token - const token = await AuthManager.generateToken(user.id, user.email, user.role) - + const token = await AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET) + // Set HTTP-only cookie setCookie(c, 'auth_token', token, { httpOnly: true, @@ -884,7 +884,7 @@ authRoutes.post('/accept-invitation', async (c) => { ).run() // Generate JWT token for auto-login - const authToken = await AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role) + const authToken = await AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role, c.env.JWT_SECRET) // Set HTTP-only cookie setCookie(c, 'auth_token', authToken, {