fix(security): harden jwt config and password rotation

This commit is contained in:
shuaiplus
2026-07-02 17:20:51 +08:00
parent 439683d350
commit 60dd298dee
18 changed files with 46 additions and 54 deletions
+3 -6
View File
@@ -1,4 +1,4 @@
import { Env, User, DEFAULT_DEV_SECRET } from '../types';
import { Env, User } from '../types';
import { StorageService } from '../services/storage';
import { AuthService } from '../services/auth';
import { RateLimitService, getClientIdentifier } from '../services/ratelimit';
@@ -150,10 +150,9 @@ function normalizeMasterPasswordHint(input: string | null | undefined): string |
return normalized ? normalized : null;
}
function jwtSecretUnsafeReason(env: Env): 'missing' | 'default' | 'too_short' | null {
function jwtSecretUnsafeReason(env: Env): 'missing' | 'too_short' | null {
const secret = (env.JWT_SECRET || '').trim();
if (!secret) return 'missing';
if (secret === DEFAULT_DEV_SECRET) return 'default';
if (secret.length < LIMITS.auth.jwtSecretMinLength) return 'too_short';
return null;
}
@@ -242,9 +241,7 @@ export async function handleRegister(request: Request, env: Env): Promise<Respon
if (unsafe) {
const message = unsafe === 'missing'
? 'JWT_SECRET is not set'
: unsafe === 'default'
? 'JWT_SECRET is using the default/sample value. Please change it.'
: 'JWT_SECRET must be at least 32 characters';
: 'JWT_SECRET must be at least 32 characters';
return errorResponse(message, 400);
}
+4 -6
View File
@@ -1,4 +1,4 @@
import { Env, Attachment, Cipher, DEFAULT_DEV_SECRET } from '../types';
import { Env, Attachment, Cipher } from '../types';
import { notifyUserCipherUpdate, notifyUserVaultSync } from '../durable/notifications-hub';
import { StorageService } from '../services/storage';
import { jsonResponse, errorResponse } from '../utils/response';
@@ -405,10 +405,8 @@ export async function handlePublicDownloadAttachment(
cipherId: string,
attachmentId: string
): Promise<Response> {
const secret = (env.JWT_SECRET || '').trim();
if (!secret || secret.length < LIMITS.auth.jwtSecretMinLength || secret === DEFAULT_DEV_SECRET) {
return errorResponse('Server configuration error', 500);
}
const secret = getSafeJwtSecret(env);
if (!secret) return errorResponse('Server configuration error', 500);
const url = new URL(request.url);
const token = url.searchParams.get('token');
@@ -418,7 +416,7 @@ export async function handlePublicDownloadAttachment(
}
// Verify token
const claims = await verifyFileDownloadToken(token, env.JWT_SECRET);
const claims = await verifyFileDownloadToken(token, secret);
if (!claims) {
return errorResponse('Invalid or expired token', 401);
}
+3 -5
View File
@@ -3,7 +3,6 @@ import { StorageService } from '../services/storage';
import { RateLimitService, getClientIdentifier } from '../services/ratelimit';
import { jsonResponse, errorResponse } from '../utils/response';
import { sanitizeDownloadContentType } from '../utils/content-type';
import { LIMITS } from '../config/limits';
import {
createSendAccessToken,
createSendFileDownloadToken,
@@ -113,10 +112,9 @@ export async function handleAccessSendFile(
idOrAccessId: string,
fileId: string
): Promise<Response> {
const secret = (env.JWT_SECRET || '').trim();
if (!secret || secret.length < LIMITS.auth.jwtSecretMinLength) {
return errorResponse('Server configuration error', 500);
}
const safeSecret = getSafeJwtSecret(env);
if (!safeSecret.ok) return safeSecret.response;
const { secret } = safeSecret;
const storage = new StorageService(env.DB);
const send = await resolveSendFromIdOrAccessId(storage, idOrAccessId);
+2 -2
View File
@@ -1,4 +1,4 @@
import { Env, Send, SendAuthType, SendResponse, SendType, DEFAULT_DEV_SECRET } from '../types';
import { Env, Send, SendAuthType, SendResponse, SendType } from '../types';
import {
notifyUserSendCreate,
notifyUserSendDelete,
@@ -371,7 +371,7 @@ export function hasEmailAuth(send: Send): boolean {
export function getSafeJwtSecret(env: Env): { ok: true; secret: string } | { ok: false; response: Response } {
const secret = (env.JWT_SECRET || '').trim();
if (!secret || secret.length < LIMITS.auth.jwtSecretMinLength || secret === DEFAULT_DEV_SECRET) {
if (!secret || secret.length < LIMITS.auth.jwtSecretMinLength) {
return { ok: false, response: errorResponse('Server configuration error', 500) };
}
return { ok: true, secret };