mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: enhance deployment process and update dependencies
- Updated the deployment script to build the web application before deploying. - Upgraded Wrangler dependency from 4.61.1 to 4.69.0. feat: add import item limit and request body size limit - Introduced a new limit for the maximum total items allowed in a single import (5000). - Set a hard body size limit for JSON API endpoints (25 MB). feat: validate KDF parameters during registration and password change - Added validation for KDF parameters to ensure compliance with Bitwarden's minimum requirements. - Enhanced error handling for invalid KDF parameters during user registration and password change. feat: clean up R2 files on user deletion - Implemented cleanup of R2 files associated with user attachments and sends before deleting user metadata. feat: verify folder ownership when creating or updating ciphers - Added checks to ensure that users cannot reference folders owned by other users when creating or updating ciphers. fix: handle corrupted cipher data gracefully - Improved error handling when retrieving ciphers from the database to avoid crashes due to corrupted data. feat: increment send access count atomically - Added a method to atomically increment the access count for sends and return whether the update was successful. fix: enforce request body size limits - Implemented checks to reject oversized request bodies for non-file upload paths. fix: update error handling for database initialization - Enhanced error logging for database initialization failures while providing a generic message to clients. feat: enhance security with Content Security Policy - Added a Content Security Policy to the web application to improve security against XSS attacks. fix: remove plaintext TOTP secret from localStorage - Updated the TOTP enabling process to remove the plaintext secret from localStorage after it is stored on the server. fix: ensure only PBKDF2 hash is sent for public send access - Modified the public send access payload to ensure only the PBKDF2 hash is sent, never the plaintext password.
This commit is contained in:
@@ -18,6 +18,32 @@ function looksLikeEncString(value: string): boolean {
|
||||
return parts.length >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate KDF parameters according to Bitwarden minimum requirements.
|
||||
* Returns an error message if invalid, or null if OK.
|
||||
*/
|
||||
function validateKdfParams(kdfType: number | undefined, kdfIterations: number | undefined, kdfMemory?: number | undefined, kdfParallelism?: number | undefined): string | null {
|
||||
const type = kdfType ?? 0;
|
||||
if (type === 0) {
|
||||
// PBKDF2-SHA256: minimum 100 000 iterations
|
||||
if (typeof kdfIterations === 'number' && kdfIterations < 100_000) {
|
||||
return 'PBKDF2 iterations must be at least 100000';
|
||||
}
|
||||
} else if (type === 1) {
|
||||
// Argon2id: iterations >= 2, memory >= 16 MiB, parallelism >= 1
|
||||
if (typeof kdfIterations === 'number' && kdfIterations < 2) {
|
||||
return 'Argon2id iterations must be at least 2';
|
||||
}
|
||||
if (typeof kdfMemory === 'number' && kdfMemory < 16) {
|
||||
return 'Argon2id memory must be at least 16 MiB';
|
||||
}
|
||||
if (typeof kdfParallelism === 'number' && kdfParallelism < 1) {
|
||||
return 'Argon2id parallelism must be at least 1';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeTotpSecret(input: string): string {
|
||||
return input.toUpperCase().replace(/[\s-]/g, '').replace(/=+$/g, '');
|
||||
}
|
||||
@@ -111,6 +137,9 @@ export async function handleRegister(request: Request, env: Env): Promise<Respon
|
||||
if (!email || !masterPasswordHash || !key) {
|
||||
return errorResponse('Email, masterPasswordHash, and key are required', 400);
|
||||
}
|
||||
if (!email.includes('@') || email.length < 3) {
|
||||
return errorResponse('Invalid email address', 400);
|
||||
}
|
||||
if (!privateKey || !publicKey) {
|
||||
return errorResponse('Private key and public key are required', 400);
|
||||
}
|
||||
@@ -121,6 +150,9 @@ export async function handleRegister(request: Request, env: Env): Promise<Respon
|
||||
return errorResponse('encryptedPrivateKey is not a valid encrypted string', 400);
|
||||
}
|
||||
|
||||
const kdfErr = validateKdfParams(body.kdf, body.kdfIterations, body.kdfMemory, body.kdfParallelism);
|
||||
if (kdfErr) return errorResponse(kdfErr, 400);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const auth = new AuthService(env);
|
||||
const serverHash = await auth.hashPasswordServer(masterPasswordHash, email);
|
||||
@@ -338,6 +370,9 @@ export async function handleChangePassword(request: Request, env: Env, userId: s
|
||||
return errorResponse('new encryptedPrivateKey is not a valid encrypted string', 400);
|
||||
}
|
||||
|
||||
const kdfErr = validateKdfParams(body.kdf ?? user.kdfType, body.kdfIterations, body.kdfMemory, body.kdfParallelism);
|
||||
if (kdfErr) return errorResponse(kdfErr, 400);
|
||||
|
||||
user.masterPasswordHash = await auth.hashPasswordServer(body.newMasterPasswordHash, user.email);
|
||||
if (nextKey) user.key = nextKey;
|
||||
if (nextPrivateKey) user.privateKey = nextPrivateKey;
|
||||
@@ -350,6 +385,15 @@ export async function handleChangePassword(request: Request, env: Env, userId: s
|
||||
user.updatedAt = new Date().toISOString();
|
||||
await storage.saveUser(user);
|
||||
await storage.deleteRefreshTokensByUserId(user.id);
|
||||
await storage.createAuditLog({
|
||||
id: generateUUID(),
|
||||
actorUserId: user.id,
|
||||
action: 'user.password.change',
|
||||
targetType: 'user',
|
||||
targetId: user.id,
|
||||
metadata: JSON.stringify({ email: user.email }),
|
||||
createdAt: user.updatedAt,
|
||||
});
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user