mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-04 22:40:11 +00:00
fix(security): scope storage reads by user
This commit is contained in:
@@ -1,151 +0,0 @@
|
|||||||
name: Sync upstream
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 3 * * *"
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
target_commit:
|
|
||||||
description: 'Commit hash (leave blank to use latest commit)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configure git
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Add upstream
|
|
||||||
run: |
|
|
||||||
git remote add upstream https://github.com/shuaiplus/NodeWarden.git || true
|
|
||||||
git fetch upstream --tags
|
|
||||||
|
|
||||||
- name: Resolve target commit
|
|
||||||
id: resolve
|
|
||||||
run: |
|
|
||||||
TRIGGER="${{ github.event_name }}"
|
|
||||||
MANUAL_INPUT="${{ github.event.inputs.target_commit }}"
|
|
||||||
|
|
||||||
if [ "$TRIGGER" = "schedule" ]; then
|
|
||||||
# Auto mode: resolve latest upstream release tag
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/shuaiplus/NodeWarden/releases/latest | jq -r .tag_name)
|
|
||||||
if [ "$LATEST_TAG" = "null" ] || [ -z "$LATEST_TAG" ]; then
|
|
||||||
echo "No release found in upstream."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
TARGET_SHA=$(git rev-list -n 1 "$LATEST_TAG" 2>/dev/null)
|
|
||||||
if [ -z "$TARGET_SHA" ]; then
|
|
||||||
echo "Tag '$LATEST_TAG' not found after fetch."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
echo "mode=auto"
|
|
||||||
echo "latest_tag=$LATEST_TAG"
|
|
||||||
echo "target_sha=$TARGET_SHA"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Auto mode — latest release: $LATEST_TAG ($TARGET_SHA)"
|
|
||||||
|
|
||||||
elif [ -n "$MANUAL_INPUT" ]; then
|
|
||||||
# Manual mode: use provided commit hash or tag
|
|
||||||
TARGET_SHA=$(git rev-parse "$MANUAL_INPUT" 2>/dev/null)
|
|
||||||
if [ -z "$TARGET_SHA" ]; then
|
|
||||||
echo "Cannot resolve '$MANUAL_INPUT' to a commit."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
echo "mode=manual"
|
|
||||||
echo "target_sha=$TARGET_SHA"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Manual mode — target: $MANUAL_INPUT ($TARGET_SHA)"
|
|
||||||
|
|
||||||
else
|
|
||||||
# Manual mode, blank input: use latest commit on upstream/main
|
|
||||||
TARGET_SHA=$(git rev-parse upstream/main)
|
|
||||||
{
|
|
||||||
echo "mode=manual"
|
|
||||||
echo "target_sha=$TARGET_SHA"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Manual mode — latest commit: $TARGET_SHA"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check if update is needed
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
TARGET_SHA="${{ steps.resolve.outputs.target_sha }}"
|
|
||||||
MODE="${{ steps.resolve.outputs.mode }}"
|
|
||||||
|
|
||||||
if [ "$MODE" = "manual" ]; then
|
|
||||||
# Manual: skip only if HEAD is exactly this commit
|
|
||||||
CURRENT_SHA=$(git rev-parse HEAD)
|
|
||||||
if [ "$CURRENT_SHA" = "$TARGET_SHA" ]; then
|
|
||||||
echo "Already at $TARGET_SHA — skipping."
|
|
||||||
echo "needs_update=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "Switching to $TARGET_SHA"
|
|
||||||
echo "needs_update=true" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Auto: skip if target is already in ancestry
|
|
||||||
if git merge-base --is-ancestor "$TARGET_SHA" HEAD 2>/dev/null; then
|
|
||||||
echo "Already up to date with $TARGET_SHA — skipping."
|
|
||||||
echo "needs_update=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "Update needed — target: $TARGET_SHA"
|
|
||||||
echo "needs_update=true" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Apply update
|
|
||||||
if: steps.check.outputs.needs_update == 'true'
|
|
||||||
run: |
|
|
||||||
TARGET_SHA="${{ steps.resolve.outputs.target_sha }}"
|
|
||||||
MODE="${{ steps.resolve.outputs.mode }}"
|
|
||||||
git checkout main
|
|
||||||
if [ "$MODE" = "manual" ]; then
|
|
||||||
# Hard reset allows both upgrade and rollback
|
|
||||||
git reset --hard "$TARGET_SHA"
|
|
||||||
else
|
|
||||||
git merge "$TARGET_SHA" --no-edit
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Restore workflow file
|
|
||||||
if: steps.check.outputs.needs_update == 'true'
|
|
||||||
run: |
|
|
||||||
# Always keep our own workflow file, never let upstream overwrite it
|
|
||||||
git checkout 'HEAD@{1}' -- .github/workflows/sync-upstream.yml 2>/dev/null || true
|
|
||||||
if ! git diff --cached --quiet; then
|
|
||||||
git commit -m "chore: restore sync-upstream workflow after sync"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Push
|
|
||||||
if: steps.check.outputs.needs_update == 'true'
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.resolve.outputs.mode }}" = "manual" ]; then
|
|
||||||
git push origin main --force
|
|
||||||
else
|
|
||||||
git push origin main
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.check.outputs.needs_update }}" = "true" ]; then
|
|
||||||
{
|
|
||||||
echo "### Synced successfully"
|
|
||||||
echo "- **Mode:** ${{ steps.resolve.outputs.mode }}"
|
|
||||||
echo "- **Tag:** ${{ steps.resolve.outputs.latest_tag || 'N/A (manual)' }}"
|
|
||||||
echo "- **Commit:** \`${{ steps.resolve.outputs.target_sha }}\`"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
else
|
|
||||||
echo "### Nothing to update" >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
fi
|
|
||||||
@@ -60,6 +60,7 @@ NodeWarden-compat/
|
|||||||
|
|
||||||
# Compatibility analysis documents
|
# Compatibility analysis documents
|
||||||
BITWARDEN_COMPATIBILITY_ANALYSIS.md
|
BITWARDEN_COMPATIBILITY_ANALYSIS.md
|
||||||
|
security-audits/
|
||||||
.mcp.json
|
.mcp.json
|
||||||
opencode.jsonc
|
opencode.jsonc
|
||||||
.cursor/
|
.cursor/
|
||||||
|
|||||||
+15
-15
@@ -167,7 +167,7 @@ export async function handleCreateAttachment(
|
|||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
|
|
||||||
// Verify cipher exists and belongs to user
|
// 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) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,7 @@ export async function handleCreateAttachment(
|
|||||||
await storage.saveAttachment(attachment);
|
await storage.saveAttachment(attachment);
|
||||||
|
|
||||||
// Add attachment to cipher
|
// Add attachment to cipher
|
||||||
await storage.addAttachmentToCipher(cipherId, attachmentId);
|
await storage.addAttachmentToCipherForUser(cipherId, attachmentId, userId);
|
||||||
|
|
||||||
// Update cipher revision date
|
// Update cipher revision date
|
||||||
const revisionInfo = await storage.updateCipherRevisionDate(cipherId);
|
const revisionInfo = await storage.updateCipherRevisionDate(cipherId);
|
||||||
@@ -215,7 +215,7 @@ export async function handleCreateAttachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get updated cipher for response
|
// 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 attachments = await storage.getAttachmentsByCipher(cipherId);
|
||||||
const jwtSecret = getSafeJwtSecret(env);
|
const jwtSecret = getSafeJwtSecret(env);
|
||||||
if (!jwtSecret) {
|
if (!jwtSecret) {
|
||||||
@@ -244,13 +244,13 @@ export async function handleUploadAttachment(
|
|||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
|
|
||||||
// Verify cipher exists and belongs to user
|
// 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) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify attachment exists
|
// Verify attachment exists
|
||||||
const attachment = await storage.getAttachment(attachmentId);
|
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
|
||||||
if (!attachment || attachment.cipherId !== cipherId) {
|
if (!attachment || attachment.cipherId !== cipherId) {
|
||||||
return errorResponse('Attachment not found', 404);
|
return errorResponse('Attachment not found', 404);
|
||||||
}
|
}
|
||||||
@@ -283,12 +283,12 @@ export async function handlePublicUploadAttachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const storage = new StorageService(env.DB);
|
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) {
|
if (!cipher || cipher.userId !== claims.userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
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) {
|
if (!attachment || attachment.cipherId !== cipherId) {
|
||||||
return errorResponse('Attachment not found', 404);
|
return errorResponse('Attachment not found', 404);
|
||||||
}
|
}
|
||||||
@@ -308,13 +308,13 @@ export async function handleGetAttachment(
|
|||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
|
|
||||||
// Verify cipher exists and belongs to user
|
// 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) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify attachment exists
|
// Verify attachment exists
|
||||||
const attachment = await storage.getAttachment(attachmentId);
|
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
|
||||||
if (!attachment || attachment.cipherId !== cipherId) {
|
if (!attachment || attachment.cipherId !== cipherId) {
|
||||||
return errorResponse('Attachment not found', 404);
|
return errorResponse('Attachment not found', 404);
|
||||||
}
|
}
|
||||||
@@ -349,12 +349,12 @@ export async function handleUpdateAttachmentMetadata(
|
|||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
|
|
||||||
const cipher = await storage.getCipher(cipherId);
|
const cipher = await storage.getCipherForUser(cipherId, userId);
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = await storage.getAttachment(attachmentId);
|
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
|
||||||
if (!attachment || attachment.cipherId !== cipherId) {
|
if (!attachment || attachment.cipherId !== cipherId) {
|
||||||
return errorResponse('Attachment not found', 404);
|
return errorResponse('Attachment not found', 404);
|
||||||
}
|
}
|
||||||
@@ -471,13 +471,13 @@ export async function handleDeleteAttachment(
|
|||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
|
|
||||||
// Verify cipher exists and belongs to user
|
// 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) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify attachment exists
|
// Verify attachment exists
|
||||||
const attachment = await storage.getAttachment(attachmentId);
|
const attachment = await storage.getAttachmentForUser(attachmentId, userId);
|
||||||
if (!attachment || attachment.cipherId !== cipherId) {
|
if (!attachment || attachment.cipherId !== cipherId) {
|
||||||
return errorResponse('Attachment not found', 404);
|
return errorResponse('Attachment not found', 404);
|
||||||
}
|
}
|
||||||
@@ -486,7 +486,7 @@ export async function handleDeleteAttachment(
|
|||||||
await deleteBlobObject(env, path);
|
await deleteBlobObject(env, path);
|
||||||
|
|
||||||
// Delete attachment metadata
|
// Delete attachment metadata
|
||||||
await storage.deleteAttachment(attachmentId);
|
await storage.deleteAttachmentForUser(attachmentId, userId);
|
||||||
|
|
||||||
// Update cipher revision date
|
// Update cipher revision date
|
||||||
const revisionInfo = await storage.updateCipherRevisionDate(cipherId);
|
const revisionInfo = await storage.updateCipherRevisionDate(cipherId);
|
||||||
@@ -501,7 +501,7 @@ export async function handleDeleteAttachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get updated cipher for response
|
// 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 attachments = await storage.getAttachmentsByCipher(cipherId);
|
||||||
const cipherResponse = cipherToResponse(updatedCipher!, attachments);
|
const cipherResponse = cipherToResponse(updatedCipher!, attachments);
|
||||||
|
|
||||||
|
|||||||
@@ -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> {
|
export async function handleGetAuthRequest(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
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);
|
if (!authRequest || authRequest.userId !== userId) return errorResponse('Not found', 404);
|
||||||
return jsonResponse(toAuthRequestResponse(request, authRequest));
|
return jsonResponse(toAuthRequestResponse(request, authRequest));
|
||||||
}
|
}
|
||||||
@@ -239,7 +239,7 @@ export async function handleUpdateAuthRequest(request: Request, env: Env, userId
|
|||||||
const body = await readJsonBody(request);
|
const body = await readJsonBody(request);
|
||||||
if (!body) return errorResponse('Invalid request payload', 400);
|
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)) {
|
if (!authRequest || authRequest.userId !== userId || isAuthRequestExpired(authRequest)) {
|
||||||
return errorResponse('Not found', 404);
|
return errorResponse('Not found', 404);
|
||||||
}
|
}
|
||||||
@@ -275,7 +275,7 @@ export async function handleUpdateAuthRequest(request: Request, env: Env, userId
|
|||||||
masterPasswordHash,
|
masterPasswordHash,
|
||||||
});
|
});
|
||||||
if (!updated) return errorResponse('Auth request has already been answered.', 409);
|
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
|
// Match Bitwarden upstream behavior: only approval wakes the originating anonymous
|
||||||
// client. Denials are not pushed to avoid leaking that a login attempt was rejected.
|
// client. Denials are not pushed to avoid leaking that a login attempt was rejected.
|
||||||
if (approved) {
|
if (approved) {
|
||||||
|
|||||||
+11
-11
@@ -812,7 +812,7 @@ export async function handleGetCiphers(request: Request, env: Env, userId: strin
|
|||||||
// GET /api/ciphers/:id
|
// GET /api/ciphers/:id
|
||||||
export async function handleGetCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleGetCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
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> {
|
async function verifyFolderOwnership(storage: StorageService, folderId: string | null | undefined, userId: string): Promise<boolean> {
|
||||||
if (!folderId) return true;
|
if (!folderId) return true;
|
||||||
const folder = await storage.getFolder(folderId);
|
const folder = await storage.getFolderForUser(folderId, userId);
|
||||||
return !!(folder && folder.userId === userId);
|
return !!folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/ciphers
|
// POST /api/ciphers
|
||||||
@@ -909,7 +909,7 @@ export async function handleCreateCipher(request: Request, env: Env, userId: str
|
|||||||
// PUT /api/ciphers/:id
|
// PUT /api/ciphers/:id
|
||||||
export async function handleUpdateCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleUpdateCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const existingCipher = await storage.getCipher(id);
|
const existingCipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!existingCipher || existingCipher.userId !== userId) {
|
if (!existingCipher || existingCipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
@@ -1020,7 +1020,7 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str
|
|||||||
// DELETE /api/ciphers/:id
|
// DELETE /api/ciphers/:id
|
||||||
export async function handleDeleteCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleDeleteCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
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.
|
// - If item is already soft-deleted -> hard delete.
|
||||||
export async function handleDeleteCipherCompat(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleDeleteCipherCompat(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
@@ -1079,7 +1079,7 @@ export async function handleDeleteCipherCompat(request: Request, env: Env, userI
|
|||||||
// DELETE /api/ciphers/:id (permanent)
|
// DELETE /api/ciphers/:id (permanent)
|
||||||
export async function handlePermanentDeleteCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handlePermanentDeleteCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
@@ -1104,7 +1104,7 @@ export async function handlePermanentDeleteCipher(request: Request, env: Env, us
|
|||||||
// PUT /api/ciphers/:id/restore
|
// PUT /api/ciphers/:id/restore
|
||||||
export async function handleRestoreCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleRestoreCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
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
|
// PUT /api/ciphers/:id/partial - Update only favorite/folderId
|
||||||
export async function handlePartialUpdateCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handlePartialUpdateCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
@@ -1218,7 +1218,7 @@ function parseCipherIdList(body: { ids?: unknown }): string[] | null {
|
|||||||
// PUT/POST /api/ciphers/:id/archive
|
// PUT/POST /api/ciphers/:id/archive
|
||||||
export async function handleArchiveCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleArchiveCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
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
|
// PUT/POST /api/ciphers/:id/unarchive
|
||||||
export async function handleUnarchiveCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleUnarchiveCipher(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const cipher = await storage.getCipher(id);
|
const cipher = await storage.getCipherForUser(id, userId);
|
||||||
|
|
||||||
if (!cipher || cipher.userId !== userId) {
|
if (!cipher || cipher.userId !== userId) {
|
||||||
return errorResponse('Cipher not found', 404);
|
return errorResponse('Cipher not found', 404);
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export async function handleGetFolders(request: Request, env: Env, userId: strin
|
|||||||
// GET /api/folders/:id
|
// GET /api/folders/:id
|
||||||
export async function handleGetFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleGetFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const folder = await storage.getFolder(id);
|
const folder = await storage.getFolderForUser(id, userId);
|
||||||
|
|
||||||
if (!folder || folder.userId !== userId) {
|
if (!folder || folder.userId !== userId) {
|
||||||
return errorResponse('Folder not found', 404);
|
return errorResponse('Folder not found', 404);
|
||||||
@@ -129,7 +129,7 @@ export async function handleCreateFolder(request: Request, env: Env, userId: str
|
|||||||
// PUT /api/folders/:id
|
// PUT /api/folders/:id
|
||||||
export async function handleUpdateFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleUpdateFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const folder = await storage.getFolder(id);
|
const folder = await storage.getFolderForUser(id, userId);
|
||||||
|
|
||||||
if (!folder || folder.userId !== userId) {
|
if (!folder || folder.userId !== userId) {
|
||||||
return errorResponse('Folder not found', 404);
|
return errorResponse('Folder not found', 404);
|
||||||
@@ -163,7 +163,7 @@ export async function handleUpdateFolder(request: Request, env: Env, userId: str
|
|||||||
// DELETE /api/folders/:id
|
// DELETE /api/folders/:id
|
||||||
export async function handleDeleteFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
export async function handleDeleteFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const folder = await storage.getFolder(id);
|
const folder = await storage.getFolderForUser(id, userId);
|
||||||
|
|
||||||
if (!folder || folder.userId !== userId) {
|
if (!folder || folder.userId !== userId) {
|
||||||
return errorResponse('Folder not found', 404);
|
return errorResponse('Folder not found', 404);
|
||||||
@@ -204,8 +204,8 @@ export async function handleBulkDeleteFolders(request: Request, env: Env, userId
|
|||||||
|
|
||||||
const folders = (
|
const folders = (
|
||||||
await Promise.all(ids.map(async (id) => {
|
await Promise.all(ids.map(async (id) => {
|
||||||
const folder = await storage.getFolder(id);
|
const folder = await storage.getFolderForUser(id, userId);
|
||||||
return folder && folder.userId === userId ? folder : null;
|
return folder;
|
||||||
}))
|
}))
|
||||||
).filter((folder): folder is Folder => !!folder);
|
).filter((folder): folder is Folder => !!folder);
|
||||||
const revisionDate = await storage.bulkDeleteFolders(ids, userId);
|
const revisionDate = await storage.bulkDeleteFolders(ids, userId);
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
|||||||
let valid = false;
|
let valid = false;
|
||||||
const normalizedAuthRequestId = String(authRequestId || '').trim();
|
const normalizedAuthRequestId = String(authRequestId || '').trim();
|
||||||
if (normalizedAuthRequestId) {
|
if (normalizedAuthRequestId) {
|
||||||
const authRequest = await storage.getAuthRequestById(normalizedAuthRequestId);
|
const authRequest = await storage.getAuthRequestByIdForUser(normalizedAuthRequestId, user.id);
|
||||||
valid = !!(
|
valid = !!(
|
||||||
authRequest &&
|
authRequest &&
|
||||||
authRequest.userId === user.id &&
|
authRequest.userId === user.id &&
|
||||||
|
|||||||
@@ -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> {
|
export async function handleGetSend(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
|
||||||
void request;
|
void request;
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
|
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found', 404);
|
return errorResponse('Send not found', 404);
|
||||||
@@ -401,7 +401,7 @@ export async function handleGetSendFileUpload(
|
|||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
void request;
|
void request;
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found', 404);
|
return errorResponse('Send not found', 404);
|
||||||
}
|
}
|
||||||
@@ -436,7 +436,7 @@ export async function handleUploadSendFile(
|
|||||||
fileId: string
|
fileId: string
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found. Unable to save the file.', 404);
|
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 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) {
|
if (!send || send.userId !== claims.userId) {
|
||||||
return errorResponse('Send not found. Unable to save the file.', 404);
|
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> {
|
export async function handleUpdateSend(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found', 404);
|
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> {
|
export async function handleDeleteSend(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found', 404);
|
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> {
|
export async function handleRemoveSendPassword(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found', 404);
|
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> {
|
export async function handleRemoveSendAuth(request: Request, env: Env, userId: string, sendId: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const send = await storage.getSend(sendId);
|
const send = await storage.getSendForUser(sendId, userId);
|
||||||
if (!send || send.userId !== userId) {
|
if (!send || send.userId !== userId) {
|
||||||
return errorResponse('Send not found', 404);
|
return errorResponse('Send not found', 404);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,35 @@ export async function getAttachment(db: D1Database, id: string): Promise<Attachm
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAttachmentForUser(db: D1Database, id: string, userId: string): Promise<Attachment | null> {
|
||||||
|
const row = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT a.id, a.cipher_id, a.file_name, a.size, a.size_name, a.key
|
||||||
|
FROM attachments a
|
||||||
|
INNER JOIN ciphers c ON c.id = a.cipher_id
|
||||||
|
WHERE a.id = ? AND c.user_id = ?`
|
||||||
|
)
|
||||||
|
.bind(id, userId)
|
||||||
|
.first<any>();
|
||||||
|
if (!row) return null;
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
cipherId: row.cipher_id,
|
||||||
|
fileName: row.file_name,
|
||||||
|
size: row.size,
|
||||||
|
sizeName: row.size_name,
|
||||||
|
key: row.key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveAttachment(db: D1Database, safeBind: SafeBind, attachment: Attachment): Promise<void> {
|
export async function saveAttachment(db: D1Database, safeBind: SafeBind, attachment: Attachment): Promise<void> {
|
||||||
const stmt = db.prepare(
|
const stmt = db.prepare(
|
||||||
'INSERT INTO attachments(id, cipher_id, file_name, size, size_name, key) VALUES(?, ?, ?, ?, ?, ?) ' +
|
'INSERT INTO attachments(id, cipher_id, file_name, size, size_name, key) VALUES(?, ?, ?, ?, ?, ?) ' +
|
||||||
'ON CONFLICT(id) DO UPDATE SET cipher_id=excluded.cipher_id, file_name=excluded.file_name, size=excluded.size, size_name=excluded.size_name, key=excluded.key'
|
'ON CONFLICT(id) DO UPDATE SET cipher_id=excluded.cipher_id, file_name=excluded.file_name, size=excluded.size, size_name=excluded.size_name, key=excluded.key ' +
|
||||||
|
'WHERE EXISTS (' +
|
||||||
|
'SELECT 1 FROM ciphers current_cipher INNER JOIN ciphers next_cipher ON next_cipher.id = excluded.cipher_id ' +
|
||||||
|
'WHERE current_cipher.id = attachments.cipher_id AND current_cipher.user_id = next_cipher.user_id' +
|
||||||
|
')'
|
||||||
);
|
);
|
||||||
await safeBind(stmt, attachment.id, attachment.cipherId, attachment.fileName, attachment.size, attachment.sizeName, attachment.key).run();
|
await safeBind(stmt, attachment.id, attachment.cipherId, attachment.fileName, attachment.size, attachment.sizeName, attachment.key).run();
|
||||||
}
|
}
|
||||||
@@ -34,6 +59,20 @@ export async function deleteAttachment(db: D1Database, id: string): Promise<void
|
|||||||
await db.prepare('DELETE FROM attachments WHERE id = ?').bind(id).run();
|
await db.prepare('DELETE FROM attachments WHERE id = ?').bind(id).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteAttachmentForUser(db: D1Database, id: string, userId: string): Promise<void> {
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`DELETE FROM attachments
|
||||||
|
WHERE id = ?
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM ciphers c
|
||||||
|
WHERE c.id = attachments.cipher_id AND c.user_id = ?
|
||||||
|
)`
|
||||||
|
)
|
||||||
|
.bind(id, userId)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
export async function bulkDeleteAttachmentsByIds(
|
export async function bulkDeleteAttachmentsByIds(
|
||||||
db: D1Database,
|
db: D1Database,
|
||||||
sqlChunkSize: SqlChunkSize,
|
sqlChunkSize: SqlChunkSize,
|
||||||
@@ -135,6 +174,30 @@ export async function addAttachmentToCipher(db: D1Database, cipherId: string, at
|
|||||||
await db.prepare('UPDATE attachments SET cipher_id = ? WHERE id = ?').bind(cipherId, attachmentId).run();
|
await db.prepare('UPDATE attachments SET cipher_id = ? WHERE id = ?').bind(cipherId, attachmentId).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addAttachmentToCipherForUser(
|
||||||
|
db: D1Database,
|
||||||
|
cipherId: string,
|
||||||
|
attachmentId: string,
|
||||||
|
userId: string
|
||||||
|
): Promise<void> {
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE attachments
|
||||||
|
SET cipher_id = ?
|
||||||
|
WHERE id = ?
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM ciphers target_cipher
|
||||||
|
WHERE target_cipher.id = ? AND target_cipher.user_id = ?
|
||||||
|
)
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM ciphers current_cipher
|
||||||
|
WHERE current_cipher.id = attachments.cipher_id AND current_cipher.user_id = ?
|
||||||
|
)`
|
||||||
|
)
|
||||||
|
.bind(cipherId, attachmentId, cipherId, userId, userId)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteAllAttachmentsByCipher(db: D1Database, cipherId: string): Promise<void> {
|
export async function deleteAllAttachmentsByCipher(db: D1Database, cipherId: string): Promise<void> {
|
||||||
await db.prepare('DELETE FROM attachments WHERE cipher_id = ?').bind(cipherId).run();
|
await db.prepare('DELETE FROM attachments WHERE cipher_id = ?').bind(cipherId).run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ export async function getAuthRequestById(db: D1Database, id: string): Promise<Au
|
|||||||
return row ? mapAuthRequestRow(row) : null;
|
return row ? mapAuthRequestRow(row) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAuthRequestByIdForUser(db: D1Database, id: string, userId: string): Promise<AuthRequestRecord | null> {
|
||||||
|
const row = await db.prepare(`${AUTH_REQUEST_SELECT} WHERE id = ? AND user_id = ? LIMIT 1`).bind(id, userId).first<any>();
|
||||||
|
return row ? mapAuthRequestRow(row) : null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function listAuthRequestsByUserId(db: D1Database, userId: string): Promise<AuthRequestRecord[]> {
|
export async function listAuthRequestsByUserId(db: D1Database, userId: string): Promise<AuthRequestRecord[]> {
|
||||||
const res = await db.prepare(`${AUTH_REQUEST_SELECT} WHERE user_id = ? ORDER BY creation_date DESC`).bind(userId).all<any>();
|
const res = await db.prepare(`${AUTH_REQUEST_SELECT} WHERE user_id = ? ORDER BY creation_date DESC`).bind(userId).all<any>();
|
||||||
return (res.results || []).map(mapAuthRequestRow);
|
return (res.results || []).map(mapAuthRequestRow);
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ export async function getCipher(db: D1Database, id: string): Promise<Cipher | nu
|
|||||||
return parseCipherRow(row);
|
return parseCipherRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCipherForUser(db: D1Database, id: string, userId: string): Promise<Cipher | null> {
|
||||||
|
const row = await db
|
||||||
|
.prepare(`SELECT ${selectCipherColumns()} FROM ciphers WHERE id = ? AND user_id = ?`)
|
||||||
|
.bind(id, userId)
|
||||||
|
.first<CipherRow>();
|
||||||
|
return parseCipherRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveCipher(db: D1Database, safeBind: SafeBind, cipher: Cipher): Promise<void> {
|
export async function saveCipher(db: D1Database, safeBind: SafeBind, cipher: Cipher): Promise<void> {
|
||||||
const folderId = normalizeOptionalId(cipher.folderId);
|
const folderId = normalizeOptionalId(cipher.folderId);
|
||||||
const data = buildCipherData(cipher, folderId);
|
const data = buildCipherData(cipher, folderId);
|
||||||
@@ -114,7 +122,8 @@ export async function saveCipher(db: D1Database, safeBind: SafeBind, cipher: Cip
|
|||||||
'INSERT INTO ciphers(id, user_id, type, folder_id, name, notes, favorite, data, reprompt, key, created_at, updated_at, archived_at, deleted_at) ' +
|
'INSERT INTO ciphers(id, user_id, type, folder_id, name, notes, favorite, data, reprompt, key, created_at, updated_at, archived_at, deleted_at) ' +
|
||||||
'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' +
|
'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' +
|
||||||
'ON CONFLICT(id) DO UPDATE SET ' +
|
'ON CONFLICT(id) DO UPDATE SET ' +
|
||||||
'user_id=excluded.user_id, type=excluded.type, folder_id=excluded.folder_id, name=excluded.name, notes=excluded.notes, favorite=excluded.favorite, data=excluded.data, reprompt=excluded.reprompt, key=excluded.key, updated_at=excluded.updated_at, archived_at=excluded.archived_at, deleted_at=excluded.deleted_at'
|
'type=excluded.type, folder_id=excluded.folder_id, name=excluded.name, notes=excluded.notes, favorite=excluded.favorite, data=excluded.data, reprompt=excluded.reprompt, key=excluded.key, updated_at=excluded.updated_at, archived_at=excluded.archived_at, deleted_at=excluded.deleted_at ' +
|
||||||
|
'WHERE user_id=excluded.user_id'
|
||||||
);
|
);
|
||||||
await safeBind(
|
await safeBind(
|
||||||
stmt,
|
stmt,
|
||||||
|
|||||||
@@ -19,11 +19,20 @@ export async function getFolder(db: D1Database, id: string): Promise<Folder | nu
|
|||||||
return mapFolderRow(row);
|
return mapFolderRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getFolderForUser(db: D1Database, id: string, userId: string): Promise<Folder | null> {
|
||||||
|
const row = await db
|
||||||
|
.prepare('SELECT id, user_id, name, created_at, updated_at FROM folders WHERE id = ? AND user_id = ?')
|
||||||
|
.bind(id, userId)
|
||||||
|
.first<any>();
|
||||||
|
if (!row) return null;
|
||||||
|
return mapFolderRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveFolder(db: D1Database, folder: Folder): Promise<void> {
|
export async function saveFolder(db: D1Database, folder: Folder): Promise<void> {
|
||||||
await db
|
await db
|
||||||
.prepare(
|
.prepare(
|
||||||
'INSERT INTO folders(id, user_id, name, created_at, updated_at) VALUES(?, ?, ?, ?, ?) ' +
|
'INSERT INTO folders(id, user_id, name, created_at, updated_at) VALUES(?, ?, ?, ?, ?) ' +
|
||||||
'ON CONFLICT(id) DO UPDATE SET user_id=excluded.user_id, name=excluded.name, updated_at=excluded.updated_at'
|
'ON CONFLICT(id) DO UPDATE SET name=excluded.name, updated_at=excluded.updated_at WHERE user_id=excluded.user_id'
|
||||||
)
|
)
|
||||||
.bind(folder.id, folder.userId, folder.name, folder.createdAt, folder.updatedAt)
|
.bind(folder.id, folder.userId, folder.name, folder.createdAt, folder.updatedAt)
|
||||||
.run();
|
.run();
|
||||||
|
|||||||
@@ -40,15 +40,27 @@ export async function getSend(db: D1Database, id: string): Promise<Send | null>
|
|||||||
return mapSendRow(row);
|
return mapSendRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSendForUser(db: D1Database, id: string, userId: string): Promise<Send | null> {
|
||||||
|
const row = await db
|
||||||
|
.prepare(
|
||||||
|
'SELECT id, user_id, type, name, notes, data, key, password_hash, password_salt, password_iterations, auth_type, emails, max_access_count, access_count, disabled, hide_email, created_at, updated_at, expiration_date, deletion_date FROM sends WHERE id = ? AND user_id = ?'
|
||||||
|
)
|
||||||
|
.bind(id, userId)
|
||||||
|
.first<any>();
|
||||||
|
if (!row) return null;
|
||||||
|
return mapSendRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveSend(db: D1Database, safeBind: SafeBind, send: Send): Promise<void> {
|
export async function saveSend(db: D1Database, safeBind: SafeBind, send: Send): Promise<void> {
|
||||||
const stmt = db.prepare(
|
const stmt = db.prepare(
|
||||||
'INSERT INTO sends(id, user_id, type, name, notes, data, key, password_hash, password_salt, password_iterations, auth_type, emails, max_access_count, access_count, disabled, hide_email, created_at, updated_at, expiration_date, deletion_date) ' +
|
'INSERT INTO sends(id, user_id, type, name, notes, data, key, password_hash, password_salt, password_iterations, auth_type, emails, max_access_count, access_count, disabled, hide_email, created_at, updated_at, expiration_date, deletion_date) ' +
|
||||||
'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' +
|
'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' +
|
||||||
'ON CONFLICT(id) DO UPDATE SET ' +
|
'ON CONFLICT(id) DO UPDATE SET ' +
|
||||||
'user_id=excluded.user_id, type=excluded.type, name=excluded.name, notes=excluded.notes, data=excluded.data, key=excluded.key, ' +
|
'type=excluded.type, name=excluded.name, notes=excluded.notes, data=excluded.data, key=excluded.key, ' +
|
||||||
'password_hash=excluded.password_hash, password_salt=excluded.password_salt, password_iterations=excluded.password_iterations, auth_type=excluded.auth_type, emails=excluded.emails, ' +
|
'password_hash=excluded.password_hash, password_salt=excluded.password_salt, password_iterations=excluded.password_iterations, auth_type=excluded.auth_type, emails=excluded.emails, ' +
|
||||||
'max_access_count=excluded.max_access_count, access_count=excluded.access_count, disabled=excluded.disabled, hide_email=excluded.hide_email, ' +
|
'max_access_count=excluded.max_access_count, access_count=excluded.access_count, disabled=excluded.disabled, hide_email=excluded.hide_email, ' +
|
||||||
'updated_at=excluded.updated_at, expiration_date=excluded.expiration_date, deletion_date=excluded.deletion_date'
|
'updated_at=excluded.updated_at, expiration_date=excluded.expiration_date, deletion_date=excluded.deletion_date ' +
|
||||||
|
'WHERE user_id=excluded.user_id'
|
||||||
);
|
);
|
||||||
|
|
||||||
await safeBind(
|
await safeBind(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
deleteFolder as deleteStoredFolder,
|
deleteFolder as deleteStoredFolder,
|
||||||
getAllFolders as listStoredFolders,
|
getAllFolders as listStoredFolders,
|
||||||
getFolder as findStoredFolder,
|
getFolder as findStoredFolder,
|
||||||
|
getFolderForUser as findStoredFolderForUser,
|
||||||
getFoldersPage as listStoredFoldersPage,
|
getFoldersPage as listStoredFoldersPage,
|
||||||
saveFolder as saveStoredFolder,
|
saveFolder as saveStoredFolder,
|
||||||
} from './storage-folder-repo';
|
} from './storage-folder-repo';
|
||||||
@@ -53,6 +54,7 @@ import {
|
|||||||
bulkUnarchiveCiphers as unarchiveStoredCiphers,
|
bulkUnarchiveCiphers as unarchiveStoredCiphers,
|
||||||
getAllCiphers as listStoredCiphers,
|
getAllCiphers as listStoredCiphers,
|
||||||
getCipher as findStoredCipher,
|
getCipher as findStoredCipher,
|
||||||
|
getCipherForUser as findStoredCipherForUser,
|
||||||
getCiphersByIds as listStoredCiphersByIds,
|
getCiphersByIds as listStoredCiphersByIds,
|
||||||
getCiphersPage as listStoredCiphersPage,
|
getCiphersPage as listStoredCiphersPage,
|
||||||
saveCipher as saveStoredCipher,
|
saveCipher as saveStoredCipher,
|
||||||
@@ -60,10 +62,13 @@ import {
|
|||||||
} from './storage-cipher-repo';
|
} from './storage-cipher-repo';
|
||||||
import {
|
import {
|
||||||
addAttachmentToCipher as attachStoredAttachmentToCipher,
|
addAttachmentToCipher as attachStoredAttachmentToCipher,
|
||||||
|
addAttachmentToCipherForUser as attachStoredAttachmentToCipherForUser,
|
||||||
bulkDeleteAttachmentsByIds as deleteStoredAttachmentsByIds,
|
bulkDeleteAttachmentsByIds as deleteStoredAttachmentsByIds,
|
||||||
deleteAllAttachmentsByCipher as deleteStoredAttachmentsByCipher,
|
deleteAllAttachmentsByCipher as deleteStoredAttachmentsByCipher,
|
||||||
deleteAttachment as deleteStoredAttachment,
|
deleteAttachment as deleteStoredAttachment,
|
||||||
|
deleteAttachmentForUser as deleteStoredAttachmentForUser,
|
||||||
getAttachment as findStoredAttachment,
|
getAttachment as findStoredAttachment,
|
||||||
|
getAttachmentForUser as findStoredAttachmentForUser,
|
||||||
getAttachmentsByCipher as listStoredAttachmentsByCipher,
|
getAttachmentsByCipher as listStoredAttachmentsByCipher,
|
||||||
getAttachmentsByCipherIds as listStoredAttachmentsByCipherIds,
|
getAttachmentsByCipherIds as listStoredAttachmentsByCipherIds,
|
||||||
getAttachmentsByUserId as listStoredAttachmentsByUserId,
|
getAttachmentsByUserId as listStoredAttachmentsByUserId,
|
||||||
@@ -75,6 +80,7 @@ import {
|
|||||||
deleteSend as deleteStoredSend,
|
deleteSend as deleteStoredSend,
|
||||||
getAllSends as listStoredSends,
|
getAllSends as listStoredSends,
|
||||||
getSend as findStoredSend,
|
getSend as findStoredSend,
|
||||||
|
getSendForUser as findStoredSendForUser,
|
||||||
getSendsByIds as listStoredSendsByIds,
|
getSendsByIds as listStoredSendsByIds,
|
||||||
getSendsPage as listStoredSendsPage,
|
getSendsPage as listStoredSendsPage,
|
||||||
incrementSendAccessCount as incrementStoredSendAccessCount,
|
incrementSendAccessCount as incrementStoredSendAccessCount,
|
||||||
@@ -114,6 +120,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
createAuthRequest as createStoredAuthRequest,
|
createAuthRequest as createStoredAuthRequest,
|
||||||
getAuthRequestById as findStoredAuthRequestById,
|
getAuthRequestById as findStoredAuthRequestById,
|
||||||
|
getAuthRequestByIdForUser as findStoredAuthRequestByIdForUser,
|
||||||
listAuthRequestsByUserId as listStoredAuthRequestsByUserId,
|
listAuthRequestsByUserId as listStoredAuthRequestsByUserId,
|
||||||
listPendingAuthRequestsByUserId as listStoredPendingAuthRequestsByUserId,
|
listPendingAuthRequestsByUserId as listStoredPendingAuthRequestsByUserId,
|
||||||
markAuthRequestAuthenticated as markStoredAuthRequestAuthenticated,
|
markAuthRequestAuthenticated as markStoredAuthRequestAuthenticated,
|
||||||
@@ -458,6 +465,10 @@ export class StorageService {
|
|||||||
return findStoredCipher(this.db, id);
|
return findStoredCipher(this.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCipherForUser(id: string, userId: string): Promise<Cipher | null> {
|
||||||
|
return findStoredCipherForUser(this.db, id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async saveCipher(cipher: Cipher): Promise<void> {
|
async saveCipher(cipher: Cipher): Promise<void> {
|
||||||
await saveStoredCipher(this.db, this.safeBind.bind(this), cipher);
|
await saveStoredCipher(this.db, this.safeBind.bind(this), cipher);
|
||||||
}
|
}
|
||||||
@@ -508,6 +519,10 @@ export class StorageService {
|
|||||||
return findStoredFolder(this.db, id);
|
return findStoredFolder(this.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFolderForUser(id: string, userId: string): Promise<Folder | null> {
|
||||||
|
return findStoredFolderForUser(this.db, id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async saveFolder(folder: Folder): Promise<void> {
|
async saveFolder(folder: Folder): Promise<void> {
|
||||||
await saveStoredFolder(this.db, folder);
|
await saveStoredFolder(this.db, folder);
|
||||||
}
|
}
|
||||||
@@ -546,6 +561,10 @@ export class StorageService {
|
|||||||
return findStoredAttachment(this.db, id);
|
return findStoredAttachment(this.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAttachmentForUser(id: string, userId: string): Promise<Attachment | null> {
|
||||||
|
return findStoredAttachmentForUser(this.db, id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async saveAttachment(attachment: Attachment): Promise<void> {
|
async saveAttachment(attachment: Attachment): Promise<void> {
|
||||||
await saveStoredAttachment(this.db, this.safeBind.bind(this), attachment);
|
await saveStoredAttachment(this.db, this.safeBind.bind(this), attachment);
|
||||||
}
|
}
|
||||||
@@ -554,6 +573,10 @@ export class StorageService {
|
|||||||
await deleteStoredAttachment(this.db, id);
|
await deleteStoredAttachment(this.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteAttachmentForUser(id: string, userId: string): Promise<void> {
|
||||||
|
await deleteStoredAttachmentForUser(this.db, id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async bulkDeleteAttachmentsByIds(ids: string[]): Promise<void> {
|
async bulkDeleteAttachmentsByIds(ids: string[]): Promise<void> {
|
||||||
await deleteStoredAttachmentsByIds(this.db, this.sqlChunkSize.bind(this), ids);
|
await deleteStoredAttachmentsByIds(this.db, this.sqlChunkSize.bind(this), ids);
|
||||||
}
|
}
|
||||||
@@ -574,6 +597,10 @@ export class StorageService {
|
|||||||
await attachStoredAttachmentToCipher(this.db, cipherId, attachmentId);
|
await attachStoredAttachmentToCipher(this.db, cipherId, attachmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addAttachmentToCipherForUser(cipherId: string, attachmentId: string, userId: string): Promise<void> {
|
||||||
|
await attachStoredAttachmentToCipherForUser(this.db, cipherId, attachmentId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async deleteAllAttachmentsByCipher(cipherId: string): Promise<void> {
|
async deleteAllAttachmentsByCipher(cipherId: string): Promise<void> {
|
||||||
await deleteStoredAttachmentsByCipher(this.db, cipherId);
|
await deleteStoredAttachmentsByCipher(this.db, cipherId);
|
||||||
}
|
}
|
||||||
@@ -634,6 +661,10 @@ export class StorageService {
|
|||||||
return findStoredSend(this.db, id);
|
return findStoredSend(this.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSendForUser(id: string, userId: string): Promise<Send | null> {
|
||||||
|
return findStoredSendForUser(this.db, id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async saveSend(send: Send): Promise<void> {
|
async saveSend(send: Send): Promise<void> {
|
||||||
await saveStoredSend(this.db, this.safeBind.bind(this), send);
|
await saveStoredSend(this.db, this.safeBind.bind(this), send);
|
||||||
}
|
}
|
||||||
@@ -783,6 +814,10 @@ export class StorageService {
|
|||||||
return findStoredAuthRequestById(this.db, id);
|
return findStoredAuthRequestById(this.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAuthRequestByIdForUser(id: string, userId: string): Promise<AuthRequestRecord | null> {
|
||||||
|
return findStoredAuthRequestByIdForUser(this.db, id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async listAuthRequestsByUserId(userId: string): Promise<AuthRequestRecord[]> {
|
async listAuthRequestsByUserId(userId: string): Promise<AuthRequestRecord[]> {
|
||||||
return listStoredAuthRequestsByUserId(this.db, userId);
|
return listStoredAuthRequestsByUserId(this.db, userId);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user