mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: enhance cipher handling with nested object merging and additional fields
This commit is contained in:
@@ -13,6 +13,26 @@ function normalizeOptionalId(value: unknown): string | null {
|
||||
return normalized ? normalized : null;
|
||||
}
|
||||
|
||||
function mergeCipherNestedObject<T>(
|
||||
existingValue: T | null | undefined,
|
||||
incomingValue: unknown
|
||||
): T | null {
|
||||
if (incomingValue === undefined) {
|
||||
return (existingValue ?? null) as T | null;
|
||||
}
|
||||
if (incomingValue === null || typeof incomingValue !== 'object' || Array.isArray(incomingValue)) {
|
||||
return incomingValue as T | null;
|
||||
}
|
||||
const existingObject =
|
||||
existingValue && typeof existingValue === 'object' && !Array.isArray(existingValue)
|
||||
? (existingValue as Record<string, unknown>)
|
||||
: {};
|
||||
return {
|
||||
...existingObject,
|
||||
...(incomingValue as Record<string, unknown>),
|
||||
} as T;
|
||||
}
|
||||
|
||||
async function notifyVaultSyncForRequest(
|
||||
request: Request,
|
||||
env: Env,
|
||||
@@ -302,6 +322,11 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str
|
||||
archivedAt: readCipherArchivedAt(cipherData, existingCipher.archivedAt ?? null),
|
||||
deletedAt: existingCipher.deletedAt,
|
||||
};
|
||||
cipher.login = mergeCipherNestedObject(existingCipher.login, cipherData.login);
|
||||
cipher.card = mergeCipherNestedObject(existingCipher.card, cipherData.card);
|
||||
cipher.identity = mergeCipherNestedObject(existingCipher.identity, cipherData.identity);
|
||||
cipher.secureNote = mergeCipherNestedObject(existingCipher.secureNote, cipherData.secureNote);
|
||||
cipher.sshKey = mergeCipherNestedObject(existingCipher.sshKey, cipherData.sshKey);
|
||||
|
||||
// Custom fields deletion compatibility:
|
||||
// - Accept both camelCase "fields" and PascalCase "Fields".
|
||||
|
||||
@@ -165,7 +165,7 @@ export function websiteIconUrl(host: string): string {
|
||||
}
|
||||
|
||||
export function createEmptyLoginUri(): VaultDraftLoginUri {
|
||||
return { uri: '', match: null };
|
||||
return { uri: '', match: null, originalUri: '', extra: {} };
|
||||
}
|
||||
|
||||
export function websiteMatchLabel(value: number | null | undefined): string {
|
||||
@@ -313,6 +313,10 @@ export function draftFromCipher(cipher: Cipher): VaultDraft {
|
||||
draft.loginUris = (cipher.login.uris || []).map((x) => ({
|
||||
uri: x.decUri || x.uri || '',
|
||||
match: x.match ?? null,
|
||||
originalUri: x.decUri || x.uri || '',
|
||||
extra: Object.fromEntries(
|
||||
Object.entries(x as Record<string, unknown>).filter(([key]) => !['uri', 'match', 'decUri'].includes(key))
|
||||
),
|
||||
}));
|
||||
draft.loginFido2Credentials = Array.isArray(cipher.login.fido2Credentials)
|
||||
? cipher.login.fido2Credentials.map((credential) => ({ ...credential }))
|
||||
|
||||
@@ -372,12 +372,20 @@ async function encryptUris(
|
||||
uris: VaultDraft['loginUris'],
|
||||
enc: Uint8Array,
|
||||
mac: Uint8Array
|
||||
): Promise<Array<{ uri: string | null; match: number | null }>> {
|
||||
const out: Array<{ uri: string | null; match: number | null }> = [];
|
||||
): Promise<Array<Record<string, unknown>>> {
|
||||
const out: Array<Record<string, unknown>> = [];
|
||||
for (const entry of uris || []) {
|
||||
const trimmed = String(entry?.uri || '').trim();
|
||||
if (!trimmed) continue;
|
||||
const preservedExtra =
|
||||
entry?.extra && typeof entry.extra === 'object'
|
||||
? { ...entry.extra }
|
||||
: {};
|
||||
if (String(entry?.originalUri || '').trim() !== trimmed) {
|
||||
delete preservedExtra.uriChecksum;
|
||||
}
|
||||
out.push({
|
||||
...preservedExtra,
|
||||
uri: await encryptTextValue(trimmed, enc, mac),
|
||||
match: typeof entry?.match === 'number' && Number.isFinite(entry.match) ? entry.match : null,
|
||||
});
|
||||
@@ -495,7 +503,12 @@ async function buildCipherPayload(
|
||||
cipher?.login && Array.isArray((cipher.login as any).fido2Credentials)
|
||||
? (cipher.login as any).fido2Credentials
|
||||
: draft.loginFido2Credentials;
|
||||
const existingLogin =
|
||||
cipher?.login && typeof cipher.login === 'object'
|
||||
? { ...(cipher.login as Record<string, unknown>) }
|
||||
: {};
|
||||
payload.login = {
|
||||
...existingLogin,
|
||||
username: await encryptTextValue(draft.loginUsername, keys.enc, keys.mac),
|
||||
password: await encryptTextValue(draft.loginPassword, keys.enc, keys.mac),
|
||||
totp: await encryptTextValue(draft.loginTotp, keys.enc, keys.mac),
|
||||
|
||||
@@ -170,10 +170,14 @@ export function importCipherToDraft(cipher: Record<string, unknown>, folderId: s
|
||||
return {
|
||||
uri,
|
||||
match: typeof matchRaw === 'number' && Number.isFinite(matchRaw) ? matchRaw : null,
|
||||
originalUri: uri,
|
||||
extra: Object.fromEntries(
|
||||
Object.entries(row).filter(([key]) => !['uri', 'match'].includes(key))
|
||||
),
|
||||
};
|
||||
})
|
||||
.filter((u) => !!u.uri);
|
||||
draft.loginUris = uris.length ? uris : [{ uri: '', match: null }];
|
||||
draft.loginUris = uris.length ? uris : [{ uri: '', match: null, originalUri: '', extra: {} }];
|
||||
draft.loginFido2Credentials = Array.isArray(login.fido2Credentials)
|
||||
? login.fido2Credentials.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object')
|
||||
: [];
|
||||
|
||||
@@ -29,13 +29,18 @@ export interface Folder {
|
||||
|
||||
export interface CipherLoginUri {
|
||||
uri?: string | null;
|
||||
uriChecksum?: string | null;
|
||||
match?: number | null;
|
||||
response?: unknown | null;
|
||||
decUri?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface VaultDraftLoginUri {
|
||||
uri: string;
|
||||
match: number | null;
|
||||
originalUri?: string;
|
||||
extra?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface CipherAttachment {
|
||||
@@ -60,9 +65,14 @@ export interface CipherLogin {
|
||||
totp?: string | null;
|
||||
uris?: CipherLoginUri[] | null;
|
||||
fido2Credentials?: CipherLoginPasskey[] | null;
|
||||
autofillOnPageLoad?: boolean | null;
|
||||
uri?: string | null;
|
||||
passwordRevisionDate?: string | null;
|
||||
response?: unknown | null;
|
||||
decUsername?: string;
|
||||
decPassword?: string;
|
||||
decTotp?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CipherCard {
|
||||
|
||||
Reference in New Issue
Block a user