Skip to content

Commit 415c5ce

Browse files
committed
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
1 parent 856ad75 commit 415c5ce

5 files changed

Lines changed: 21 additions & 17 deletions

File tree

packages/core/src/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface Bindings {
5656
IMAGES_ACCOUNT_ID?: string
5757
IMAGES_API_TOKEN?: string
5858
ENVIRONMENT?: string
59+
JWT_SECRET?: string
5960
BUCKET_NAME?: string
6061
GOOGLE_MAPS_API_KEY?: string
6162
REQUIRE_API_KEY?: string

packages/core/src/middleware/auth.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ type JWTPayload = {
1010
iat: number
1111
}
1212

13-
// JWT secret - in production this should come from environment variables
14-
const JWT_SECRET = 'your-super-secret-jwt-key-change-in-production'
13+
// Fallback JWT secret for local development only (no wrangler secret set)
14+
const JWT_SECRET_FALLBACK = 'your-super-secret-jwt-key-change-in-production'
1515

1616
export class AuthManager {
17-
static async generateToken(userId: string, email: string, role: string): Promise<string> {
17+
static async generateToken(userId: string, email: string, role: string, secret?: string): Promise<string> {
1818
const payload: JWTPayload = {
1919
userId,
2020
email,
2121
role,
2222
exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24), // 24 hours
2323
iat: Math.floor(Date.now() / 1000)
2424
}
25-
26-
return await sign(payload, JWT_SECRET, 'HS256')
25+
26+
return await sign(payload, secret || JWT_SECRET_FALLBACK, 'HS256')
2727
}
2828

29-
static async verifyToken(token: string): Promise<JWTPayload | null> {
29+
static async verifyToken(token: string, secret?: string): Promise<JWTPayload | null> {
3030
try {
31-
const payload = await verify(token, JWT_SECRET, 'HS256') as JWTPayload
31+
const payload = await verify(token, secret || JWT_SECRET_FALLBACK, 'HS256') as JWTPayload
3232

3333
// Check if token is expired
3434
if (payload.exp < Math.floor(Date.now() / 1000)) {
@@ -198,7 +198,8 @@ export const requireAuth = () => {
198198

199199
// If not cached, verify token
200200
if (!payload) {
201-
payload = await AuthManager.verifyToken(token)
201+
const jwtSecret = (c.env as any)?.JWT_SECRET
202+
payload = await AuthManager.verifyToken(token, jwtSecret)
202203

203204
// Cache the verified payload for 5 minutes
204205
if (payload && kv) {
@@ -272,7 +273,8 @@ export const optionalAuth = () => {
272273
}
273274

274275
if (token) {
275-
const payload = await AuthManager.verifyToken(token)
276+
const jwtSecret = (c.env as any)?.JWT_SECRET
277+
const payload = await AuthManager.verifyToken(token, jwtSecret)
276278
if (payload) {
277279
c.set('user', payload)
278280
}

packages/core/src/plugins/available/magic-link-auth/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ export function createMagicLinkAuthPlugin(): Plugin {
204204
const jwtToken = await AuthManager.generateToken(
205205
user.id,
206206
user.email,
207-
user.role
207+
user.role,
208+
(c.env as any).JWT_SECRET
208209
)
209210

210211
// Set auth cookie

packages/core/src/plugins/core-plugins/otp-login-plugin/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ export function createOTPLoginPlugin(): Plugin {
282282
}
283283

284284
// Generate JWT token
285-
const token = await AuthManager.generateToken(user.id, user.email, user.role)
285+
const token = await AuthManager.generateToken(user.id, user.email, user.role, (c.env as any).JWT_SECRET)
286286

287287
// Set HTTP-only cookie
288288
setCookie(c, 'auth_token', token, {

packages/core/src/routes/auth.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ authRoutes.post('/register',
150150
).run()
151151

152152
// Generate JWT token
153-
const token = await AuthManager.generateToken(userId, normalizedEmail, 'viewer')
153+
const token = await AuthManager.generateToken(userId, normalizedEmail, 'viewer', c.env.JWT_SECRET)
154154

155155
// Set HTTP-only cookie
156156
setCookie(c, 'auth_token', token, {
@@ -238,7 +238,7 @@ authRoutes.post('/login', async (c) => {
238238
}
239239

240240
// Generate JWT token
241-
const token = await AuthManager.generateToken(user.id, user.email, user.role)
241+
const token = await AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET)
242242

243243
// Set HTTP-only cookie
244244
setCookie(c, 'auth_token', token, {
@@ -335,7 +335,7 @@ authRoutes.post('/refresh', requireAuth(), async (c) => {
335335
}
336336

337337
// Generate new token
338-
const token = await AuthManager.generateToken(user.userId, user.email, user.role)
338+
const token = await AuthManager.generateToken(user.userId, user.email, user.role, c.env.JWT_SECRET)
339339

340340
// Set new cookie
341341
setCookie(c, 'auth_token', token, {
@@ -448,7 +448,7 @@ authRoutes.post('/register/form', async (c) => {
448448
).run()
449449

450450
// Generate JWT token
451-
const token = await AuthManager.generateToken(userId, normalizedEmail, role)
451+
const token = await AuthManager.generateToken(userId, normalizedEmail, role, c.env.JWT_SECRET)
452452

453453
// Set HTTP-only cookie
454454
setCookie(c, 'auth_token', token, {
@@ -540,7 +540,7 @@ authRoutes.post('/login/form', async (c) => {
540540
}
541541

542542
// Generate JWT token
543-
const token = await AuthManager.generateToken(user.id, user.email, user.role)
543+
const token = await AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET)
544544

545545
// Set HTTP-only cookie
546546
setCookie(c, 'auth_token', token, {
@@ -908,7 +908,7 @@ authRoutes.post('/accept-invitation', async (c) => {
908908
).run()
909909

910910
// Generate JWT token for auto-login
911-
const authToken = await AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role)
911+
const authToken = await AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role, c.env.JWT_SECRET)
912912

913913
// Set HTTP-only cookie
914914
setCookie(c, 'auth_token', authToken, {

0 commit comments

Comments
 (0)