mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
fix: enhance attachment handling and folder deletion logic; improve error responses and rate limiting
This commit is contained in:
@@ -234,7 +234,6 @@ export async function handlePublicDownloadAttachment(
|
||||
}
|
||||
|
||||
const storage = new StorageService(env.DB);
|
||||
|
||||
|
||||
// Verify attachment exists
|
||||
const attachment = await storage.getAttachment(attachmentId);
|
||||
@@ -250,6 +249,11 @@ export async function handlePublicDownloadAttachment(
|
||||
return errorResponse('Attachment file not found', 404);
|
||||
}
|
||||
|
||||
const firstUse = await storage.consumeAttachmentDownloadToken(claims.jti, claims.exp);
|
||||
if (!firstUse) {
|
||||
return errorResponse('Invalid or expired token', 401);
|
||||
}
|
||||
|
||||
return new Response(object.body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
|
||||
@@ -103,6 +103,7 @@ export async function handleDeleteFolder(request: Request, env: Env, userId: str
|
||||
return errorResponse('Folder not found', 404);
|
||||
}
|
||||
|
||||
await storage.clearFolderFromCiphers(userId, id);
|
||||
await storage.deleteFolder(id, userId);
|
||||
await storage.updateRevisionDate(userId);
|
||||
|
||||
|
||||
@@ -12,12 +12,15 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
|
||||
let body: Record<string, string>;
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
|
||||
if (contentType.includes('application/x-www-form-urlencoded')) {
|
||||
const formData = await request.formData();
|
||||
body = Object.fromEntries(formData.entries()) as Record<string, string>;
|
||||
} else {
|
||||
body = await request.json();
|
||||
try {
|
||||
if (contentType.includes('application/x-www-form-urlencoded')) {
|
||||
const formData = await request.formData();
|
||||
body = Object.fromEntries(formData.entries()) as Record<string, string>;
|
||||
} else {
|
||||
body = await request.json();
|
||||
}
|
||||
} catch {
|
||||
return identityErrorResponse('Invalid request payload', 'invalid_request', 400);
|
||||
}
|
||||
|
||||
const grantType = body.grant_type;
|
||||
@@ -108,12 +111,12 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
// Refresh token
|
||||
const refreshToken = body.refresh_token;
|
||||
if (!refreshToken) {
|
||||
return errorResponse('Refresh token is required', 400);
|
||||
return identityErrorResponse('Refresh token is required', 'invalid_request', 400);
|
||||
}
|
||||
|
||||
const result = await auth.refreshAccessToken(refreshToken);
|
||||
if (!result) {
|
||||
return errorResponse('Invalid refresh token', 401);
|
||||
return identityErrorResponse('Invalid refresh token', 'invalid_grant', 400);
|
||||
}
|
||||
|
||||
// Revoke old refresh token (prevent reuse)
|
||||
@@ -158,7 +161,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
return jsonResponse(response);
|
||||
}
|
||||
|
||||
return errorResponse('Unsupported grant type', 400);
|
||||
return identityErrorResponse('Unsupported grant type', 'unsupported_grant_type', 400);
|
||||
}
|
||||
|
||||
// POST /identity/accounts/prelogin
|
||||
|
||||
@@ -649,8 +649,6 @@ function renderRegisterPageHTML(jwtState: JwtSecretState | null): string {
|
||||
creating: '正在创建…',
|
||||
doneTitle: '初始化完成',
|
||||
doneDesc: '服务已就绪。在 Bitwarden 客户端中填入以下服务器地址:',
|
||||
important: '重要提示',
|
||||
limitations: '本项目仅支持单用户:不能添加新用户;不支持修改主密码;如果忘记主密码,只能重新部署并重新注册。',
|
||||
hideTitle: '隐藏初始化页',
|
||||
hideDesc: '隐藏后,初始化页对任何人都会返回 404。你的密码库仍可正常使用。',
|
||||
hideBtn: '隐藏初始化页',
|
||||
@@ -738,8 +736,6 @@ function renderRegisterPageHTML(jwtState: JwtSecretState | null): string {
|
||||
creating: 'Creating…',
|
||||
doneTitle: 'Setup complete',
|
||||
doneDesc: 'Your server is ready. Use this URL in Bitwarden clients:',
|
||||
important: 'Important',
|
||||
limitations: 'Single user only: no additional users, no master password change. If forgotten, redeploy and register again.',
|
||||
hideTitle: 'Hide setup page',
|
||||
hideDesc: 'After hiding, this page returns 404 for everyone. Vault still works.',
|
||||
hideBtn: 'Hide setup page',
|
||||
@@ -843,8 +839,6 @@ function renderRegisterPageHTML(jwtState: JwtSecretState | null): string {
|
||||
setText('submitBtn', t('create'));
|
||||
setText('t_done_title', t('doneTitle'));
|
||||
setText('t_done_desc', t('doneDesc'));
|
||||
setText('t_important', t('important'));
|
||||
setText('t_limitations', t('limitations'));
|
||||
setText('t_hide_title', t('hideTitle'));
|
||||
setText('t_hide_desc', t('hideDesc'));
|
||||
setText('hideBtn', t('hideBtn'));
|
||||
|
||||
+12
-7
@@ -6,6 +6,9 @@ import { cipherToResponse } from './ciphers';
|
||||
// GET /api/sync
|
||||
export async function handleSync(request: Request, env: Env, userId: string): Promise<Response> {
|
||||
const storage = new StorageService(env.DB);
|
||||
const url = new URL(request.url);
|
||||
const excludeDomainsParam = url.searchParams.get('excludeDomains');
|
||||
const excludeDomains = excludeDomainsParam !== null && /^(1|true|yes)$/i.test(excludeDomainsParam);
|
||||
|
||||
const user = await storage.getUserById(userId);
|
||||
if (!user) {
|
||||
@@ -61,11 +64,13 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
||||
folders: folderResponses,
|
||||
collections: [],
|
||||
ciphers: cipherResponses,
|
||||
domains: {
|
||||
equivalentDomains: [],
|
||||
globalEquivalentDomains: [],
|
||||
object: 'domains',
|
||||
},
|
||||
domains: excludeDomains
|
||||
? null
|
||||
: {
|
||||
equivalentDomains: [],
|
||||
globalEquivalentDomains: [],
|
||||
object: 'domains',
|
||||
},
|
||||
policies: [],
|
||||
sends: [],
|
||||
// PascalCase for desktop/browser clients
|
||||
@@ -81,7 +86,7 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
||||
},
|
||||
MasterKeyEncryptedUserKey: user.key,
|
||||
MasterKeyWrappedUserKey: user.key,
|
||||
Salt: user.email,
|
||||
Salt: user.email.toLowerCase(),
|
||||
Object: 'masterPasswordUnlock',
|
||||
},
|
||||
},
|
||||
@@ -96,7 +101,7 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
||||
},
|
||||
masterKeyWrappedUserKey: user.key,
|
||||
masterKeyEncryptedUserKey: user.key,
|
||||
salt: user.email,
|
||||
salt: user.email.toLowerCase(),
|
||||
},
|
||||
},
|
||||
object: 'sync',
|
||||
|
||||
Reference in New Issue
Block a user