mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add normalization functions for optional IDs and public keys in cipher and user decryption handling
This commit is contained in:
+16
-6
@@ -7,6 +7,12 @@ import { deleteAllAttachmentsForCipher } from './attachments';
|
|||||||
import { parsePagination, encodeContinuationToken } from '../utils/pagination';
|
import { parsePagination, encodeContinuationToken } from '../utils/pagination';
|
||||||
import { readActingDeviceIdentifier } from '../utils/device';
|
import { readActingDeviceIdentifier } from '../utils/device';
|
||||||
|
|
||||||
|
function normalizeOptionalId(value: unknown): string | null {
|
||||||
|
if (value == null) return null;
|
||||||
|
const normalized = String(value).trim();
|
||||||
|
return normalized ? normalized : null;
|
||||||
|
}
|
||||||
|
|
||||||
async function notifyVaultSyncForRequest(
|
async function notifyVaultSyncForRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
env: Env,
|
env: Env,
|
||||||
@@ -47,6 +53,7 @@ function syncCipherComputedAliases(cipher: Cipher): Cipher {
|
|||||||
function normalizeCipherForStorage(cipher: Cipher): Cipher {
|
function normalizeCipherForStorage(cipher: Cipher): Cipher {
|
||||||
cipher.login = normalizeCipherLoginForStorage(cipher.login);
|
cipher.login = normalizeCipherLoginForStorage(cipher.login);
|
||||||
cipher.sshKey = normalizeCipherSshKeyForCompatibility(cipher.sshKey);
|
cipher.sshKey = normalizeCipherSshKeyForCompatibility(cipher.sshKey);
|
||||||
|
cipher.folderId = normalizeOptionalId(cipher.folderId);
|
||||||
const hasArchivedAt = Object.prototype.hasOwnProperty.call(cipher as object, 'archivedAt');
|
const hasArchivedAt = Object.prototype.hasOwnProperty.call(cipher as object, 'archivedAt');
|
||||||
cipher.archivedAt = hasArchivedAt
|
cipher.archivedAt = hasArchivedAt
|
||||||
? normalizeCipherTimestamp(cipher.archivedAt) ?? null
|
? normalizeCipherTimestamp(cipher.archivedAt) ?? null
|
||||||
@@ -185,6 +192,7 @@ export function cipherToResponse(
|
|||||||
// Pass through ALL stored cipher fields (known + unknown)
|
// Pass through ALL stored cipher fields (known + unknown)
|
||||||
...passthrough,
|
...passthrough,
|
||||||
// Server-computed / enforced fields (always override)
|
// Server-computed / enforced fields (always override)
|
||||||
|
folderId: normalizeOptionalId(cipher.folderId),
|
||||||
type: Number(cipher.type) || 1,
|
type: Number(cipher.type) || 1,
|
||||||
organizationId: null,
|
organizationId: null,
|
||||||
organizationUseTotp: false,
|
organizationUseTotp: false,
|
||||||
@@ -499,11 +507,12 @@ export async function handlePartialUpdateCipher(request: Request, env: Env, user
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body.folderId !== undefined) {
|
if (body.folderId !== undefined) {
|
||||||
if (body.folderId) {
|
const folderId = normalizeOptionalId(body.folderId);
|
||||||
const folderOk = await verifyFolderOwnership(storage, body.folderId, userId);
|
if (folderId) {
|
||||||
|
const folderOk = await verifyFolderOwnership(storage, folderId, userId);
|
||||||
if (!folderOk) return errorResponse('Folder not found', 404);
|
if (!folderOk) return errorResponse('Folder not found', 404);
|
||||||
}
|
}
|
||||||
cipher.folderId = body.folderId;
|
cipher.folderId = folderId;
|
||||||
}
|
}
|
||||||
if (body.favorite !== undefined) {
|
if (body.favorite !== undefined) {
|
||||||
cipher.favorite = body.favorite;
|
cipher.favorite = body.favorite;
|
||||||
@@ -537,12 +546,13 @@ export async function handleBulkMoveCiphers(request: Request, env: Env, userId:
|
|||||||
return errorResponse('ids array is required', 400);
|
return errorResponse('ids array is required', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.folderId) {
|
const folderId = normalizeOptionalId(body.folderId);
|
||||||
const folderOk = await verifyFolderOwnership(storage, body.folderId, userId);
|
if (folderId) {
|
||||||
|
const folderOk = await verifyFolderOwnership(storage, folderId, userId);
|
||||||
if (!folderOk) return errorResponse('Folder not found', 404);
|
if (!folderOk) return errorResponse('Folder not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const revisionDate = await storage.bulkMoveCiphers(body.ids, body.folderId || null, userId);
|
const revisionDate = await storage.bulkMoveCiphers(body.ids, folderId, userId);
|
||||||
if (revisionDate) {
|
if (revisionDate) {
|
||||||
await notifyVaultSyncForRequest(request, env, userId, revisionDate);
|
await notifyVaultSyncForRequest(request, env, userId, revisionDate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import type { Cipher } from '../types';
|
import type { Cipher } from '../types';
|
||||||
|
|
||||||
|
function normalizeOptionalId(value: unknown): string | null {
|
||||||
|
if (value == null) return null;
|
||||||
|
const normalized = String(value).trim();
|
||||||
|
return normalized ? normalized : null;
|
||||||
|
}
|
||||||
|
|
||||||
type SafeBind = (stmt: D1PreparedStatement, ...values: any[]) => D1PreparedStatement;
|
type SafeBind = (stmt: D1PreparedStatement, ...values: any[]) => D1PreparedStatement;
|
||||||
type SqlChunkSize = (fixedBindCount: number) => number;
|
type SqlChunkSize = (fixedBindCount: number) => number;
|
||||||
type UpdateRevisionDate = (userId: string) => Promise<string>;
|
type UpdateRevisionDate = (userId: string) => Promise<string>;
|
||||||
@@ -25,12 +31,13 @@ function parseCipherRow(row: CipherRow | null | undefined): Cipher | null {
|
|||||||
if (!row?.data) return null;
|
if (!row?.data) return null;
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(row.data) as Cipher;
|
const parsed = JSON.parse(row.data) as Cipher;
|
||||||
|
const folderId = normalizeOptionalId(row.folder_id ?? parsed.folderId ?? null);
|
||||||
return {
|
return {
|
||||||
...parsed,
|
...parsed,
|
||||||
id: row.id,
|
id: row.id,
|
||||||
userId: row.user_id,
|
userId: row.user_id,
|
||||||
type: Number(row.type) || Number(parsed.type) || 1,
|
type: Number(row.type) || Number(parsed.type) || 1,
|
||||||
folderId: row.folder_id ?? parsed.folderId ?? null,
|
folderId,
|
||||||
name: row.name ?? parsed.name ?? null,
|
name: row.name ?? parsed.name ?? null,
|
||||||
notes: row.notes ?? parsed.notes ?? null,
|
notes: row.notes ?? parsed.notes ?? null,
|
||||||
favorite: row.favorite != null ? !!row.favorite : !!parsed.favorite,
|
favorite: row.favorite != null ? !!row.favorite : !!parsed.favorite,
|
||||||
@@ -60,7 +67,11 @@ export async function getCipher(db: D1Database, id: string): Promise<Cipher | nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 data = JSON.stringify(cipher);
|
const folderId = normalizeOptionalId(cipher.folderId);
|
||||||
|
const data = JSON.stringify({
|
||||||
|
...cipher,
|
||||||
|
folderId,
|
||||||
|
});
|
||||||
const stmt = db.prepare(
|
const stmt = db.prepare(
|
||||||
'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(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' +
|
||||||
@@ -72,7 +83,7 @@ export async function saveCipher(db: D1Database, safeBind: SafeBind, cipher: Cip
|
|||||||
cipher.id,
|
cipher.id,
|
||||||
cipher.userId,
|
cipher.userId,
|
||||||
Number(cipher.type) || 1,
|
Number(cipher.type) || 1,
|
||||||
cipher.folderId,
|
folderId,
|
||||||
cipher.name,
|
cipher.name,
|
||||||
cipher.notes,
|
cipher.notes,
|
||||||
cipher.favorite ? 1 : 0,
|
cipher.favorite ? 1 : 0,
|
||||||
@@ -249,8 +260,9 @@ export async function bulkMoveCiphers(
|
|||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
if (ids.length === 0) return null;
|
if (ids.length === 0) return null;
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
const normalizedFolderId = normalizeOptionalId(folderId);
|
||||||
const uniqueIds = sanitizeIds(ids);
|
const uniqueIds = sanitizeIds(ids);
|
||||||
const patch = JSON.stringify({ folderId, updatedAt: now });
|
const patch = JSON.stringify({ folderId: normalizedFolderId, updatedAt: now });
|
||||||
const chunkSize = sqlChunkSize(4);
|
const chunkSize = sqlChunkSize(4);
|
||||||
|
|
||||||
for (let i = 0; i < uniqueIds.length; i += chunkSize) {
|
for (let i = 0; i < uniqueIds.length; i += chunkSize) {
|
||||||
@@ -262,7 +274,7 @@ export async function bulkMoveCiphers(
|
|||||||
SET folder_id = ?, updated_at = ?, data = json_patch(data, ?)
|
SET folder_id = ?, updated_at = ?, data = json_patch(data, ?)
|
||||||
WHERE user_id = ? AND id IN (${placeholders})`
|
WHERE user_id = ? AND id IN (${placeholders})`
|
||||||
)
|
)
|
||||||
.bind(folderId, now, patch, userId, ...chunk)
|
.bind(normalizedFolderId, now, patch, userId, ...chunk)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { User, UserDecryptionOptions } from '../types';
|
import { User, UserDecryptionOptions } from '../types';
|
||||||
|
|
||||||
|
function normalizeOptionalPublicKey(value: unknown): string {
|
||||||
|
if (value == null) return '';
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildAccountKeys(user: Pick<User, 'privateKey' | 'publicKey'>): Record<string, unknown> | null {
|
export function buildAccountKeys(user: Pick<User, 'privateKey' | 'publicKey'>): Record<string, unknown> | null {
|
||||||
if (!user.privateKey || !user.publicKey) {
|
if (!user.privateKey) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const publicKey = normalizeOptionalPublicKey(user.publicKey);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
publicKeyEncryptionKeyPair: {
|
publicKeyEncryptionKeyPair: {
|
||||||
wrappedPrivateKey: user.privateKey,
|
wrappedPrivateKey: user.privateKey,
|
||||||
publicKey: user.publicKey,
|
publicKey,
|
||||||
Object: 'publicKeyEncryptionKeyPair',
|
Object: 'publicKeyEncryptionKeyPair',
|
||||||
},
|
},
|
||||||
Object: 'privateKeys',
|
Object: 'privateKeys',
|
||||||
|
|||||||
Reference in New Issue
Block a user