fix(security): scope storage reads by user

This commit is contained in:
shuaiplus
2026-07-02 16:03:49 +08:00
parent ce3674669e
commit baf569983d
14 changed files with 182 additions and 199 deletions
+15 -15
View File
@@ -167,7 +167,7 @@ export async function handleCreateAttachment(
const storage = new StorageService(env.DB);
// Verify cipher exists and belongs to user
const cipher = await storage.getCipher(cipherId);
const cipher = await storage.getCipherForUser(cipherId, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
}
@@ -205,7 +205,7 @@ export async function handleCreateAttachment(
await storage.saveAttachment(attachment);
// Add attachment to cipher
await storage.addAttachmentToCipher(cipherId, attachmentId);
await storage.addAttachmentToCipherForUser(cipherId, attachmentId, userId);
// Update cipher revision date
const revisionInfo = await storage.updateCipherRevisionDate(cipherId);
@@ -215,7 +215,7 @@ export async function handleCreateAttachment(
}
// Get updated cipher for response
const updatedCipher = await storage.getCipher(cipherId);
const updatedCipher = await storage.getCipherForUser(cipherId, userId);
const attachments = await storage.getAttachmentsByCipher(cipherId);
const jwtSecret = getSafeJwtSecret(env);
if (!jwtSecret) {
@@ -244,13 +244,13 @@ export async function handleUploadAttachment(
const storage = new StorageService(env.DB);
// Verify cipher exists and belongs to user
const cipher = await storage.getCipher(cipherId);
const cipher = await storage.getCipherForUser(cipherId, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
}
// Verify attachment exists
const attachment = await storage.getAttachment(attachmentId);
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
if (!attachment || attachment.cipherId !== cipherId) {
return errorResponse('Attachment not found', 404);
}
@@ -283,12 +283,12 @@ export async function handlePublicUploadAttachment(
}
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(cipherId);
const cipher = await storage.getCipherForUser(cipherId, claims.userId);
if (!cipher || cipher.userId !== claims.userId) {
return errorResponse('Cipher not found', 404);
}
const attachment = await storage.getAttachment(attachmentId);
const attachment = await storage.getAttachmentForUser(attachmentId, claims.userId);
if (!attachment || attachment.cipherId !== cipherId) {
return errorResponse('Attachment not found', 404);
}
@@ -308,13 +308,13 @@ export async function handleGetAttachment(
const storage = new StorageService(env.DB);
// Verify cipher exists and belongs to user
const cipher = await storage.getCipher(cipherId);
const cipher = await storage.getCipherForUser(cipherId, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
}
// Verify attachment exists
const attachment = await storage.getAttachment(attachmentId);
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
if (!attachment || attachment.cipherId !== cipherId) {
return errorResponse('Attachment not found', 404);
}
@@ -349,12 +349,12 @@ export async function handleUpdateAttachmentMetadata(
): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(cipherId);
const cipher = await storage.getCipherForUser(cipherId, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
}
const attachment = await storage.getAttachment(attachmentId);
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
if (!attachment || attachment.cipherId !== cipherId) {
return errorResponse('Attachment not found', 404);
}
@@ -471,13 +471,13 @@ export async function handleDeleteAttachment(
const storage = new StorageService(env.DB);
// Verify cipher exists and belongs to user
const cipher = await storage.getCipher(cipherId);
const cipher = await storage.getCipherForUser(cipherId, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
}
// Verify attachment exists
const attachment = await storage.getAttachment(attachmentId);
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
if (!attachment || attachment.cipherId !== cipherId) {
return errorResponse('Attachment not found', 404);
}
@@ -486,7 +486,7 @@ export async function handleDeleteAttachment(
await deleteBlobObject(env, path);
// Delete attachment metadata
await storage.deleteAttachment(attachmentId);
await storage.deleteAttachmentForUser(attachmentId, userId);
// Update cipher revision date
const revisionInfo = await storage.updateCipherRevisionDate(cipherId);
@@ -501,7 +501,7 @@ export async function handleDeleteAttachment(
}
// Get updated cipher for response
const updatedCipher = await storage.getCipher(cipherId);
const updatedCipher = await storage.getCipherForUser(cipherId, userId);
const attachments = await storage.getAttachmentsByCipher(cipherId);
const cipherResponse = cipherToResponse(updatedCipher!, attachments);
+3 -3
View File
@@ -201,7 +201,7 @@ export async function handleCreateAuthRequest(request: Request, env: Env): Promi
export async function handleGetAuthRequest(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const authRequest = await storage.getAuthRequestById(id);
const authRequest = await storage.getAuthRequestByIdForUser(id, userId);
if (!authRequest || authRequest.userId !== userId) return errorResponse('Not found', 404);
return jsonResponse(toAuthRequestResponse(request, authRequest));
}
@@ -239,7 +239,7 @@ export async function handleUpdateAuthRequest(request: Request, env: Env, userId
const body = await readJsonBody(request);
if (!body) return errorResponse('Invalid request payload', 400);
const authRequest = await storage.getAuthRequestById(id);
const authRequest = await storage.getAuthRequestByIdForUser(id, userId);
if (!authRequest || authRequest.userId !== userId || isAuthRequestExpired(authRequest)) {
return errorResponse('Not found', 404);
}
@@ -275,7 +275,7 @@ export async function handleUpdateAuthRequest(request: Request, env: Env, userId
masterPasswordHash,
});
if (!updated) return errorResponse('Auth request has already been answered.', 409);
const updatedRequest = await storage.getAuthRequestById(id);
const updatedRequest = await storage.getAuthRequestByIdForUser(id, userId);
// Match Bitwarden upstream behavior: only approval wakes the originating anonymous
// client. Denials are not pushed to avoid leaking that a login attempt was rejected.
if (approved) {
+11 -11
View File
@@ -812,7 +812,7 @@ export async function handleGetCiphers(request: Request, env: Env, userId: strin
// GET /api/ciphers/:id
export async function handleGetCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -827,8 +827,8 @@ export async function handleGetCipher(request: Request, env: Env, userId: string
async function verifyFolderOwnership(storage: StorageService, folderId: string | null | undefined, userId: string): Promise<boolean> {
if (!folderId) return true;
const folder = await storage.getFolder(folderId);
return !!(folder && folder.userId === userId);
const folder = await storage.getFolderForUser(folderId, userId);
return !!folder;
}
// POST /api/ciphers
@@ -909,7 +909,7 @@ export async function handleCreateCipher(request: Request, env: Env, userId: str
// PUT /api/ciphers/:id
export async function handleUpdateCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const existingCipher = await storage.getCipher(id);
const existingCipher = await storage.getCipherForUser(id, userId);
if (!existingCipher || existingCipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1020,7 +1020,7 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str
// DELETE /api/ciphers/:id
export async function handleDeleteCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1052,7 +1052,7 @@ export async function handleDeleteCipher(request: Request, env: Env, userId: str
// - If item is already soft-deleted -> hard delete.
export async function handleDeleteCipherCompat(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1079,7 +1079,7 @@ export async function handleDeleteCipherCompat(request: Request, env: Env, userI
// DELETE /api/ciphers/:id (permanent)
export async function handlePermanentDeleteCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1104,7 +1104,7 @@ export async function handlePermanentDeleteCipher(request: Request, env: Env, us
// PUT /api/ciphers/:id/restore
export async function handleRestoreCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1126,7 +1126,7 @@ export async function handleRestoreCipher(request: Request, env: Env, userId: st
// PUT /api/ciphers/:id/partial - Update only favorite/folderId
export async function handlePartialUpdateCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1218,7 +1218,7 @@ function parseCipherIdList(body: { ids?: unknown }): string[] | null {
// PUT/POST /api/ciphers/:id/archive
export async function handleArchiveCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
@@ -1244,7 +1244,7 @@ export async function handleArchiveCipher(request: Request, env: Env, userId: st
// PUT/POST /api/ciphers/:id/unarchive
export async function handleUnarchiveCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const cipher = await storage.getCipher(id);
const cipher = await storage.getCipherForUser(id, userId);
if (!cipher || cipher.userId !== userId) {
return errorResponse('Cipher not found', 404);
+5 -5
View File
@@ -80,7 +80,7 @@ export async function handleGetFolders(request: Request, env: Env, userId: strin
// GET /api/folders/:id
export async function handleGetFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const folder = await storage.getFolder(id);
const folder = await storage.getFolderForUser(id, userId);
if (!folder || folder.userId !== userId) {
return errorResponse('Folder not found', 404);
@@ -129,7 +129,7 @@ export async function handleCreateFolder(request: Request, env: Env, userId: str
// PUT /api/folders/:id
export async function handleUpdateFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const folder = await storage.getFolder(id);
const folder = await storage.getFolderForUser(id, userId);
if (!folder || folder.userId !== userId) {
return errorResponse('Folder not found', 404);
@@ -163,7 +163,7 @@ export async function handleUpdateFolder(request: Request, env: Env, userId: str
// DELETE /api/folders/:id
export async function handleDeleteFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const folder = await storage.getFolder(id);
const folder = await storage.getFolderForUser(id, userId);
if (!folder || folder.userId !== userId) {
return errorResponse('Folder not found', 404);
@@ -204,8 +204,8 @@ export async function handleBulkDeleteFolders(request: Request, env: Env, userId
const folders = (
await Promise.all(ids.map(async (id) => {
const folder = await storage.getFolder(id);
return folder && folder.userId === userId ? folder : null;
const folder = await storage.getFolderForUser(id, userId);
return folder;
}))
).filter((folder): folder is Folder => !!folder);
const revisionDate = await storage.bulkDeleteFolders(ids, userId);
+1 -1
View File
@@ -341,7 +341,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
let valid = false;
const normalizedAuthRequestId = String(authRequestId || '').trim();
if (normalizedAuthRequestId) {
const authRequest = await storage.getAuthRequestById(normalizedAuthRequestId);
const authRequest = await storage.getAuthRequestByIdForUser(normalizedAuthRequestId, user.id);
valid = !!(
authRequest &&
authRequest.userId === user.id &&
+8 -8
View File
@@ -134,7 +134,7 @@ export async function handleGetSends(request: Request, env: Env, userId: string)
export async function handleGetSend(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
void request;
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found', 404);
@@ -401,7 +401,7 @@ export async function handleGetSendFileUpload(
): Promise<Response> {
void request;
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found', 404);
}
@@ -436,7 +436,7 @@ export async function handleUploadSendFile(
fileId: string
): Promise<Response> {
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found. Unable to save the file.', 404);
}
@@ -472,7 +472,7 @@ export async function handlePublicUploadSendFile(
}
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, claims.userId);
if (!send || send.userId !== claims.userId) {
return errorResponse('Send not found. Unable to save the file.', 404);
}
@@ -485,7 +485,7 @@ export async function handlePublicUploadSendFile(
export async function handleUpdateSend(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found', 404);
}
@@ -632,7 +632,7 @@ export async function handleUpdateSend(request: Request, env: Env, userId: strin
export async function handleDeleteSend(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found', 404);
}
@@ -698,7 +698,7 @@ export async function handleBulkDeleteSends(request: Request, env: Env, userId:
export async function handleRemoveSendPassword(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found', 404);
}
@@ -719,7 +719,7 @@ export async function handleRemoveSendPassword(request: Request, env: Env, userI
export async function handleRemoveSendAuth(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
const send = await storage.getSendForUser(sendId, userId);
if (!send || send.userId !== userId) {
return errorResponse('Send not found', 404);
}