mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
Refactor VaultPage component: remove exposed password checks, add bulk delete functionality for folders, and improve list rendering performance
- Removed password breach checking logic and related state management from VaultPage. - Introduced bulk delete functionality for folders with a confirmation dialog. - Enhanced list rendering with virtualization to improve performance. - Updated styles for folder actions and list items for better UI consistency. - Removed unused password breach library and related translations.
This commit is contained in:
+31
-17
@@ -34,6 +34,7 @@ import {
|
||||
handleRestoreCipher,
|
||||
handlePartialUpdateCipher,
|
||||
handleBulkMoveCiphers,
|
||||
handleBulkDeleteCiphers,
|
||||
} from './handlers/ciphers';
|
||||
|
||||
// Folder handlers
|
||||
@@ -42,7 +43,8 @@ import {
|
||||
handleGetFolder,
|
||||
handleCreateFolder,
|
||||
handleUpdateFolder,
|
||||
handleDeleteFolder
|
||||
handleDeleteFolder,
|
||||
handleBulkDeleteFolders,
|
||||
} from './handlers/folders';
|
||||
|
||||
// Send handlers
|
||||
@@ -55,6 +57,7 @@ import {
|
||||
handleUploadSendFile,
|
||||
handleUpdateSend,
|
||||
handleDeleteSend,
|
||||
handleBulkDeleteSends,
|
||||
handleRemoveSendPassword,
|
||||
handleRemoveSendAuth,
|
||||
handleAccessSend,
|
||||
@@ -141,6 +144,18 @@ function getNwIconSvg(): string {
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="NW icon"><rect x="4" y="4" width="88" height="88" rx="20" fill="#111418"/><text x="48" y="60" text-anchor="middle" font-size="36" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-weight="800" letter-spacing="0.5" fill="#FFFFFF">NW</text></svg>`;
|
||||
}
|
||||
|
||||
function isImportBypassRequest(request: Request, path: string, method: string): boolean {
|
||||
if (request.headers.get('X-NodeWarden-Import') !== '1') return false;
|
||||
|
||||
if (method === 'POST') {
|
||||
if (path === '/api/ciphers/import') return true;
|
||||
if (/^\/api\/ciphers\/[a-f0-9-]+\/attachment\/v2$/i.test(path)) return true;
|
||||
if (/^\/api\/ciphers\/[a-f0-9-]+\/attachment\/[a-f0-9-]+$/i.test(path)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleNwFavicon(): Response {
|
||||
return new Response(getNwIconSvg(), {
|
||||
status: 200,
|
||||
@@ -151,17 +166,6 @@ function handleNwFavicon(): Response {
|
||||
});
|
||||
}
|
||||
|
||||
const BITWARDEN_DEFAULT_ICON_SHA256 = 'aaa64871332ad5b7d28fe8874efb19c2d9cc2f1e6de75d52b080b438225a0783';
|
||||
|
||||
function bytesToHex(bytes: Uint8Array): string {
|
||||
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
async function sha256Hex(buffer: ArrayBuffer): Promise<string> {
|
||||
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
||||
return bytesToHex(new Uint8Array(digest));
|
||||
}
|
||||
|
||||
function isValidIconHostname(hostname: string): boolean {
|
||||
if (!hostname) return false;
|
||||
if (hostname.length > 253) return false;
|
||||
@@ -183,7 +187,7 @@ function isValidIconHostname(hostname: string): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
// Icons handler - proxy to Bitwarden's official icon service
|
||||
// Icons handler - proxy to favicon.im
|
||||
async function handleGetIcon(request: Request, env: Env, hostname: string): Promise<Response> {
|
||||
try {
|
||||
void env;
|
||||
@@ -199,8 +203,7 @@ async function handleGetIcon(request: Request, env: Env, hostname: string): Prom
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Use Bitwarden's official icon service
|
||||
const iconUrl = `https://icons.bitwarden.net/${normalizedHostname}/icon.png`;
|
||||
const iconUrl = `https://favicon.im/${normalizedHostname}`;
|
||||
const resp = await fetch(iconUrl, {
|
||||
headers: { 'User-Agent': 'NodeWarden/1.0' },
|
||||
redirect: 'follow',
|
||||
@@ -212,7 +215,7 @@ async function handleGetIcon(request: Request, env: Env, hostname: string): Prom
|
||||
|
||||
if (resp.ok) {
|
||||
const body = await resp.arrayBuffer();
|
||||
if (body.byteLength === 500 && (await sha256Hex(body)) === BITWARDEN_DEFAULT_ICON_SHA256) {
|
||||
if (body.byteLength === 0) {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
const iconResponse = new Response(body, {
|
||||
@@ -512,7 +515,7 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
||||
return errorResponse('Account is disabled', 403);
|
||||
}
|
||||
// Unified rate limiting for all authenticated API requests.
|
||||
{
|
||||
if (!isImportBypassRequest(request, path, method)) {
|
||||
const rateLimit = new RateLimitService(env.DB);
|
||||
const rateLimitCheck = await rateLimit.consumeBudget(
|
||||
userId + ':api',
|
||||
@@ -600,6 +603,10 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
||||
return handleCiphersImport(request, env, userId);
|
||||
}
|
||||
|
||||
if (path === '/api/ciphers/delete' && method === 'POST') {
|
||||
return handleBulkDeleteCiphers(request, env, userId);
|
||||
}
|
||||
|
||||
// Bulk cipher operations (only move is allowed)
|
||||
if (path === '/api/ciphers/move') {
|
||||
if (method === 'POST' || method === 'PUT') {
|
||||
@@ -677,6 +684,9 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
||||
if (method === 'GET') return handleGetFolders(request, env, userId);
|
||||
if (method === 'POST') return handleCreateFolder(request, env, userId);
|
||||
}
|
||||
if (path === '/api/folders/delete' && method === 'POST') {
|
||||
return handleBulkDeleteFolders(request, env, userId);
|
||||
}
|
||||
|
||||
// Match /api/folders/:id patterns
|
||||
const folderMatch = path.match(/^\/api\/folders\/([a-f0-9-]+)$/i);
|
||||
@@ -712,6 +722,10 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
||||
if (method === 'POST') return handleCreateSend(request, env, userId);
|
||||
}
|
||||
|
||||
if (path === '/api/sends/delete' && method === 'POST') {
|
||||
return handleBulkDeleteSends(request, env, userId);
|
||||
}
|
||||
|
||||
if ((path === '/api/sends/file/v2' || path === '/api/sends/file') && method === 'POST') {
|
||||
return handleCreateFileSendV2(request, env, userId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user