mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-05 06:50:10 +00:00
feat: support Bitwarden extended cipher types
This commit is contained in:
+90
-2
@@ -7,6 +7,9 @@ import {
|
||||
CipherResponse,
|
||||
CipherSecureNote,
|
||||
CipherSshKey,
|
||||
CipherBankAccount,
|
||||
CipherDriversLicense,
|
||||
CipherPassport,
|
||||
Attachment,
|
||||
PasswordHistory,
|
||||
} from '../types';
|
||||
@@ -254,6 +257,49 @@ function sanitizeEncryptedObject<T extends Record<string, any>>(
|
||||
return next as T;
|
||||
}
|
||||
|
||||
const BANK_ACCOUNT_ENCRYPTED_KEYS = [
|
||||
'bankName',
|
||||
'nameOnAccount',
|
||||
'accountType',
|
||||
'accountNumber',
|
||||
'routingNumber',
|
||||
'branchNumber',
|
||||
'pin',
|
||||
'swiftCode',
|
||||
'iban',
|
||||
'bankContactPhone',
|
||||
] as const;
|
||||
|
||||
const DRIVERS_LICENSE_ENCRYPTED_KEYS = [
|
||||
'firstName',
|
||||
'middleName',
|
||||
'lastName',
|
||||
'dateOfBirth',
|
||||
'licenseNumber',
|
||||
'issuingCountry',
|
||||
'issuingState',
|
||||
'issueDate',
|
||||
'expirationDate',
|
||||
'issuingAuthority',
|
||||
'licenseClass',
|
||||
] as const;
|
||||
|
||||
const PASSPORT_ENCRYPTED_KEYS = [
|
||||
'surname',
|
||||
'givenName',
|
||||
'dateOfBirth',
|
||||
'sex',
|
||||
'birthPlace',
|
||||
'nationality',
|
||||
'issuingCountry',
|
||||
'passportNumber',
|
||||
'passportType',
|
||||
'nationalIdentificationNumber',
|
||||
'issuingAuthority',
|
||||
'issueDate',
|
||||
'expirationDate',
|
||||
] as const;
|
||||
|
||||
function normalizeCipherForStorage(cipher: Cipher): Cipher {
|
||||
cipher.login = normalizeCipherLoginForStorage(cipher.login);
|
||||
cipher.sshKey = normalizeCipherSshKeyForCompatibility(cipher.sshKey);
|
||||
@@ -376,6 +422,20 @@ export function validateCipherEncryptedFieldsForCompatibility(cipher: Cipher): s
|
||||
if (fingerprint != null && !isValidEncString(fingerprint)) return 'SSH key fingerprint must be an encrypted string.';
|
||||
}
|
||||
|
||||
const typedEncryptedObjects: Array<[string, any, readonly string[]]> = [
|
||||
['Bank account', (cipher as any).bankAccount, BANK_ACCOUNT_ENCRYPTED_KEYS],
|
||||
['Drivers license', (cipher as any).driversLicense, DRIVERS_LICENSE_ENCRYPTED_KEYS],
|
||||
['Passport', (cipher as any).passport, PASSPORT_ENCRYPTED_KEYS],
|
||||
];
|
||||
for (const [label, source, keys] of typedEncryptedObjects) {
|
||||
if (!source || typeof source !== 'object') continue;
|
||||
for (const key of keys) {
|
||||
if (source[key] != null && !optionalEncStringWithin(source[key], 10000)) {
|
||||
return `${label} ${key} must be an encrypted string.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate password history — each password must be an encrypted string.
|
||||
if (Array.isArray(cipher.passwordHistory)) {
|
||||
for (const entry of cipher.passwordHistory) {
|
||||
@@ -752,7 +812,20 @@ export function cipherToResponse(
|
||||
'licenseNumber',
|
||||
]);
|
||||
const normalizedSshKey = normalizeCipherSshKeyForCompatibility((passthrough as any).sshKey ?? null);
|
||||
const normalizedSecureNote = Number(cipher.type) === 2
|
||||
const normalizedBankAccount = sanitizeEncryptedObject(
|
||||
(passthrough as any).bankAccount ?? null,
|
||||
BANK_ACCOUNT_ENCRYPTED_KEYS
|
||||
);
|
||||
const normalizedDriversLicense = sanitizeEncryptedObject(
|
||||
(passthrough as any).driversLicense ?? null,
|
||||
DRIVERS_LICENSE_ENCRYPTED_KEYS
|
||||
);
|
||||
const normalizedPassport = sanitizeEncryptedObject(
|
||||
(passthrough as any).passport ?? null,
|
||||
PASSPORT_ENCRYPTED_KEYS
|
||||
);
|
||||
const responseType = Number(cipher.type) || 1;
|
||||
const normalizedSecureNote = responseType === 2
|
||||
? normalizeCipherSecureNoteForCompatibility((passthrough as any).secureNote ?? null) ?? { type: 0 }
|
||||
: null;
|
||||
const responseAttachments = applyCipherEmbeddedAttachmentMetadata(cipher, attachments);
|
||||
@@ -763,7 +836,7 @@ export function cipherToResponse(
|
||||
...passthrough,
|
||||
// Server-computed / enforced fields (always override)
|
||||
folderId: normalizeResponseFolderId(cipher.folderId, options.validFolderIds),
|
||||
type: Number(cipher.type) || 1,
|
||||
type: responseType,
|
||||
organizationId: normalizeOptionalId((passthrough as any).organizationId ?? null),
|
||||
organizationUseTotp: !!((passthrough as any).organizationUseTotp ?? false),
|
||||
creationDate: createdAt,
|
||||
@@ -785,6 +858,9 @@ export function cipherToResponse(
|
||||
fields: normalizeCipherFieldsForCompatibility((passthrough as any).fields),
|
||||
passwordHistory: normalizePasswordHistoryForCompatibility((passthrough as any).passwordHistory),
|
||||
sshKey: normalizedSshKey,
|
||||
bankAccount: responseType === 6 ? normalizedBankAccount : null,
|
||||
driversLicense: responseType === 7 ? normalizedDriversLicense : null,
|
||||
passport: responseType === 8 ? normalizedPassport : null,
|
||||
key: responseCipherKey,
|
||||
data: typeof (passthrough as any).data === 'string' ? (passthrough as any).data : null,
|
||||
encryptedFor: (passthrough as any).encryptedFor ?? null,
|
||||
@@ -880,6 +956,9 @@ export async function handleCreateCipher(request: Request, env: Env, userId: str
|
||||
const createIdentity = readCipherProp<CipherIdentity | null>(cipherData, ['identity', 'Identity']);
|
||||
const createSecureNote = readCipherProp<CipherSecureNote | null>(cipherData, ['secureNote', 'SecureNote']);
|
||||
const createSshKey = readCipherProp<CipherSshKey | null>(cipherData, ['sshKey', 'SshKey']);
|
||||
const createBankAccount = readCipherProp<CipherBankAccount | null>(cipherData, ['bankAccount', 'BankAccount']);
|
||||
const createDriversLicense = readCipherProp<CipherDriversLicense | null>(cipherData, ['driversLicense', 'DriversLicense']);
|
||||
const createPassport = readCipherProp<CipherPassport | null>(cipherData, ['passport', 'Passport']);
|
||||
const createPasswordHistory = readCipherProp<PasswordHistory[] | null>(cipherData, ['passwordHistory', 'PasswordHistory']);
|
||||
|
||||
if (createKey.present && !shouldAcceptCipherKey(createKey.value)) {
|
||||
@@ -909,6 +988,9 @@ export async function handleCreateCipher(request: Request, env: Env, userId: str
|
||||
cipher.identity = createIdentity.present ? (createIdentity.value ?? null) : (cipher.identity ?? null);
|
||||
cipher.secureNote = createSecureNote.present ? (createSecureNote.value ?? null) : (cipher.secureNote ?? null);
|
||||
cipher.sshKey = createSshKey.present ? (createSshKey.value ?? null) : (cipher.sshKey ?? null);
|
||||
cipher.bankAccount = createBankAccount.present ? (createBankAccount.value ?? null) : ((cipher as any).bankAccount ?? null);
|
||||
cipher.driversLicense = createDriversLicense.present ? (createDriversLicense.value ?? null) : ((cipher as any).driversLicense ?? null);
|
||||
cipher.passport = createPassport.present ? (createPassport.value ?? null) : ((cipher as any).passport ?? null);
|
||||
cipher.passwordHistory = createPasswordHistory.present ? (createPasswordHistory.value ?? null) : (cipher.passwordHistory ?? null);
|
||||
const createFields = getAliasedProp(cipherData, ['fields', 'Fields']);
|
||||
cipher.fields = createFields.present ? (createFields.value ?? null) : (cipher.fields ?? null);
|
||||
@@ -960,6 +1042,9 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str
|
||||
const incomingIdentity = readCipherProp<CipherIdentity | null>(cipherData, ['identity', 'Identity']);
|
||||
const incomingSecureNote = readCipherProp<CipherSecureNote | null>(cipherData, ['secureNote', 'SecureNote']);
|
||||
const incomingSshKey = readCipherProp<CipherSshKey | null>(cipherData, ['sshKey', 'SshKey']);
|
||||
const incomingBankAccount = readCipherProp<CipherBankAccount | null>(cipherData, ['bankAccount', 'BankAccount']);
|
||||
const incomingDriversLicense = readCipherProp<CipherDriversLicense | null>(cipherData, ['driversLicense', 'DriversLicense']);
|
||||
const incomingPassport = readCipherProp<CipherPassport | null>(cipherData, ['passport', 'Passport']);
|
||||
const incomingPasswordHistory = readCipherProp<PasswordHistory[] | null>(cipherData, ['passwordHistory', 'PasswordHistory']);
|
||||
const incomingRevisionDate = readCipherRevisionDate(cipherData);
|
||||
const hasAttachmentMigrationMetadata = hasIncomingAttachmentMetadata(cipherData);
|
||||
@@ -1008,6 +1093,9 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str
|
||||
cipher.card = nextType === 3 ? (incomingCard.present ? (incomingCard.value ?? null) : (existingCipher.card ?? null)) : null;
|
||||
cipher.identity = nextType === 4 ? (incomingIdentity.present ? (incomingIdentity.value ?? null) : (existingCipher.identity ?? null)) : null;
|
||||
cipher.sshKey = nextType === 5 ? (incomingSshKey.present ? (incomingSshKey.value ?? null) : (existingCipher.sshKey ?? null)) : null;
|
||||
cipher.bankAccount = nextType === 6 ? (incomingBankAccount.present ? (incomingBankAccount.value ?? null) : ((existingCipher as any).bankAccount ?? null)) : null;
|
||||
cipher.driversLicense = nextType === 7 ? (incomingDriversLicense.present ? (incomingDriversLicense.value ?? null) : ((existingCipher as any).driversLicense ?? null)) : null;
|
||||
cipher.passport = nextType === 8 ? (incomingPassport.present ? (incomingPassport.value ?? null) : ((existingCipher as any).passport ?? null)) : null;
|
||||
if (incomingPasswordHistory.present) {
|
||||
cipher.passwordHistory = incomingPasswordHistory.value ?? null;
|
||||
}
|
||||
|
||||
+11
-1
@@ -17,6 +17,9 @@ interface CiphersImportRequest {
|
||||
favorite?: boolean;
|
||||
reprompt?: number;
|
||||
sshKey?: any | null;
|
||||
bankAccount?: any | null;
|
||||
driversLicense?: any | null;
|
||||
passport?: any | null;
|
||||
key?: string | null;
|
||||
login?: {
|
||||
uris?: Array<{ uri: string | null; uriChecksum?: string | null; match?: number | null }> | null;
|
||||
@@ -185,6 +188,10 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
|
||||
const card = readAliasedImportProp<any | null>(c, ['card', 'Card']);
|
||||
const identity = readAliasedImportProp<any | null>(c, ['identity', 'Identity']);
|
||||
const secureNote = readAliasedImportProp<any | null>(c, ['secureNote', 'SecureNote']);
|
||||
const sshKey = readAliasedImportProp<any | null>(c, ['sshKey', 'SshKey']);
|
||||
const bankAccount = readAliasedImportProp<any | null>(c, ['bankAccount', 'BankAccount']);
|
||||
const driversLicense = readAliasedImportProp<any | null>(c, ['driversLicense', 'DriversLicense']);
|
||||
const passport = readAliasedImportProp<any | null>(c, ['passport', 'Passport']);
|
||||
const fields = readAliasedImportProp<any[] | null>(c, ['fields', 'Fields']);
|
||||
const passwordHistory = readAliasedImportProp<any[] | null>(c, ['passwordHistory', 'PasswordHistory']);
|
||||
const key = readAliasedImportProp<string | null>(c, ['key', 'Key']);
|
||||
@@ -254,7 +261,10 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
|
||||
})) || null,
|
||||
passwordHistory: passwordHistory ?? null,
|
||||
reprompt: c.reprompt ?? 0,
|
||||
sshKey: normalizeCipherSshKeyForCompatibility((c as any).sshKey ?? null),
|
||||
sshKey: normalizeCipherSshKeyForCompatibility(sshKey ?? null),
|
||||
bankAccount: bankAccount ?? null,
|
||||
driversLicense: driversLicense ?? null,
|
||||
passport: passport ?? null,
|
||||
key: key ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
@@ -124,6 +124,10 @@ export enum CipherType {
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SSHKey = 5,
|
||||
BankAccount = 6,
|
||||
DriversLicense = 7,
|
||||
Passport = 8,
|
||||
}
|
||||
|
||||
export interface CipherLoginUri {
|
||||
@@ -158,6 +162,52 @@ export interface CipherSshKey {
|
||||
keyFingerprint: string;
|
||||
}
|
||||
|
||||
export interface CipherBankAccount {
|
||||
bankName: string | null;
|
||||
nameOnAccount: string | null;
|
||||
accountType: string | null;
|
||||
accountNumber: string | null;
|
||||
routingNumber: string | null;
|
||||
branchNumber: string | null;
|
||||
pin: string | null;
|
||||
swiftCode: string | null;
|
||||
iban: string | null;
|
||||
bankContactPhone: string | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CipherDriversLicense {
|
||||
firstName: string | null;
|
||||
middleName: string | null;
|
||||
lastName: string | null;
|
||||
dateOfBirth: string | null;
|
||||
licenseNumber: string | null;
|
||||
issuingCountry: string | null;
|
||||
issuingState: string | null;
|
||||
issueDate: string | null;
|
||||
expirationDate: string | null;
|
||||
issuingAuthority: string | null;
|
||||
licenseClass: string | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CipherPassport {
|
||||
surname: string | null;
|
||||
givenName: string | null;
|
||||
dateOfBirth: string | null;
|
||||
sex: string | null;
|
||||
birthPlace: string | null;
|
||||
nationality: string | null;
|
||||
issuingCountry: string | null;
|
||||
passportNumber: string | null;
|
||||
passportType: string | null;
|
||||
nationalIdentificationNumber: string | null;
|
||||
issuingAuthority: string | null;
|
||||
issueDate: string | null;
|
||||
expirationDate: string | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CipherIdentity {
|
||||
title: string | null;
|
||||
firstName: string | null;
|
||||
@@ -208,6 +258,9 @@ export interface Cipher {
|
||||
identity: CipherIdentity | null;
|
||||
secureNote: CipherSecureNote | null;
|
||||
sshKey: CipherSshKey | null;
|
||||
bankAccount?: CipherBankAccount | null;
|
||||
driversLicense?: CipherDriversLicense | null;
|
||||
passport?: CipherPassport | null;
|
||||
fields: CipherField[] | null;
|
||||
passwordHistory: PasswordHistory[] | null;
|
||||
reprompt: number;
|
||||
@@ -547,6 +600,9 @@ export interface CipherResponse {
|
||||
identity: CipherIdentity | null;
|
||||
secureNote: CipherSecureNote | null;
|
||||
sshKey: CipherSshKey | null;
|
||||
bankAccount: CipherBankAccount | null;
|
||||
driversLicense: CipherDriversLicense | null;
|
||||
passport: CipherPassport | null;
|
||||
fields: CipherField[] | null;
|
||||
passwordHistory: PasswordHistory[] | null;
|
||||
reprompt: number;
|
||||
|
||||
@@ -12,17 +12,20 @@ import {
|
||||
cardListSubtitle,
|
||||
FOLDER_SORT_STORAGE_KEY,
|
||||
VAULT_SORT_STORAGE_KEY,
|
||||
bankAccountListSubtitle,
|
||||
cipherTypeKey,
|
||||
cipherTypeLabel,
|
||||
createEmptyDraft,
|
||||
creationTimeValue,
|
||||
draftFromCipher,
|
||||
driversLicenseListSubtitle,
|
||||
buildCipherDuplicateSignatures,
|
||||
firstCipherUri,
|
||||
firstPasskeyCreationTime,
|
||||
isCipherVisibleInArchive,
|
||||
isCipherVisibleInNormalVault,
|
||||
isCipherVisibleInTrash,
|
||||
passportListSubtitle,
|
||||
sortTimeValue,
|
||||
type DuplicateDetectionMode,
|
||||
type SidebarFilter,
|
||||
@@ -308,10 +311,21 @@ export default function VaultPage(props: VaultPageProps) {
|
||||
const name = String(cipher.decName || cipher.name || '');
|
||||
const username = String(cipher.login?.decUsername || '');
|
||||
const uri = firstCipherUri(cipher);
|
||||
const typedText = [
|
||||
cipher.bankAccount?.decBankName,
|
||||
cipher.bankAccount?.decNameOnAccount,
|
||||
cipher.bankAccount?.decAccountNumber,
|
||||
cipher.driversLicense?.decLicenseNumber,
|
||||
cipher.driversLicense?.decFirstName,
|
||||
cipher.driversLicense?.decLastName,
|
||||
cipher.passport?.decPassportNumber,
|
||||
cipher.passport?.decGivenName,
|
||||
cipher.passport?.decSurname,
|
||||
].filter(Boolean).join('\n');
|
||||
const cipherId = String(cipher.id || '').trim();
|
||||
meta.set(cipher.id, {
|
||||
name,
|
||||
searchText: `${cipherId}\n${cipherId.replace(/-/g, '')}\n${name}\n${username}\n${uri}`.toLowerCase(),
|
||||
searchText: `${cipherId}\n${cipherId.replace(/-/g, '')}\n${name}\n${username}\n${uri}\n${typedText}`.toLowerCase(),
|
||||
firstUri: uri,
|
||||
typeKey: cipherTypeKey(Number(cipher.type || 1)),
|
||||
sortTime: sortTimeValue(cipher),
|
||||
@@ -542,6 +556,9 @@ const folderName = useCallback((id: string | null | undefined): string => {
|
||||
if (Number(cipher.type || 1) === 3) {
|
||||
return cardListSubtitle(cipher);
|
||||
}
|
||||
if (Number(cipher.type || 1) === 6) return bankAccountListSubtitle(cipher);
|
||||
if (Number(cipher.type || 1) === 7) return driversLicenseListSubtitle(cipher);
|
||||
if (Number(cipher.type || 1) === 8) return passportListSubtitle(cipher);
|
||||
return cipherTypeLabel(Number(cipher.type || 1));
|
||||
}, [cipherMetaById]);
|
||||
|
||||
|
||||
@@ -327,6 +327,55 @@ export default function VaultDetailView(props: VaultDetailViewProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.selectedCipher.bankAccount && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_bank_account_details')}</h4>
|
||||
<div className="kv-line"><span>{t('txt_bank_name')}</span><strong>{props.selectedCipher.bankAccount.decBankName || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_name_on_account')}</span><strong>{props.selectedCipher.bankAccount.decNameOnAccount || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_account_type')}</span><strong>{props.selectedCipher.bankAccount.decAccountType || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_account_number')}</span><strong>{props.selectedCipher.bankAccount.decAccountNumber || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_routing_number')}</span><strong>{props.selectedCipher.bankAccount.decRoutingNumber || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_branch_number')}</span><strong>{props.selectedCipher.bankAccount.decBranchNumber || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_pin')}</span><strong>{props.selectedCipher.bankAccount.decPin || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_swift_code')}</span><strong>{props.selectedCipher.bankAccount.decSwiftCode || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_iban')}</span><strong>{props.selectedCipher.bankAccount.decIban || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_bank_contact_phone')}</span><strong>{props.selectedCipher.bankAccount.decBankContactPhone || ''}</strong></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.selectedCipher.driversLicense && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_drivers_license_details')}</h4>
|
||||
<div className="kv-line"><span>{t('txt_name')}</span><strong>{[props.selectedCipher.driversLicense.decFirstName, props.selectedCipher.driversLicense.decMiddleName, props.selectedCipher.driversLicense.decLastName].filter(Boolean).join(' ')}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_date_of_birth')}</span><strong>{props.selectedCipher.driversLicense.decDateOfBirth || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_license_number')}</span><strong>{props.selectedCipher.driversLicense.decLicenseNumber || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issuing_country')}</span><strong>{props.selectedCipher.driversLicense.decIssuingCountry || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issuing_state')}</span><strong>{props.selectedCipher.driversLicense.decIssuingState || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issue_date')}</span><strong>{props.selectedCipher.driversLicense.decIssueDate || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_expiration_date')}</span><strong>{props.selectedCipher.driversLicense.decExpirationDate || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issuing_authority')}</span><strong>{props.selectedCipher.driversLicense.decIssuingAuthority || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_license_class')}</span><strong>{props.selectedCipher.driversLicense.decLicenseClass || ''}</strong></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.selectedCipher.passport && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_passport_details')}</h4>
|
||||
<div className="kv-line"><span>{t('txt_name')}</span><strong>{[props.selectedCipher.passport.decGivenName, props.selectedCipher.passport.decSurname].filter(Boolean).join(' ')}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_date_of_birth')}</span><strong>{props.selectedCipher.passport.decDateOfBirth || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_sex')}</span><strong>{props.selectedCipher.passport.decSex || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_birth_place')}</span><strong>{props.selectedCipher.passport.decBirthPlace || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_nationality')}</span><strong>{props.selectedCipher.passport.decNationality || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issuing_country')}</span><strong>{props.selectedCipher.passport.decIssuingCountry || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_passport_number')}</span><strong>{props.selectedCipher.passport.decPassportNumber || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_passport_type')}</span><strong>{props.selectedCipher.passport.decPassportType || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_national_id_number')}</span><strong>{props.selectedCipher.passport.decNationalIdentificationNumber || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issuing_authority')}</span><strong>{props.selectedCipher.passport.decIssuingAuthority || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_issue_date')}</span><strong>{props.selectedCipher.passport.decIssueDate || ''}</strong></div>
|
||||
<div className="kv-line"><span>{t('txt_expiration_date')}</span><strong>{props.selectedCipher.passport.decExpirationDate || ''}</strong></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!(props.selectedCipher.decNotes || '').trim() && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_notes')}</h4>
|
||||
|
||||
@@ -589,6 +589,64 @@ export default function VaultEditor(props: VaultEditorProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.draft.type === 6 && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_bank_account_details')}</h4>
|
||||
<div className="field-grid">
|
||||
<label className="field"><span>{t('txt_bank_name')}</span><input className="input" value={props.draft.bankName} onInput={(e) => props.onUpdateDraft({ bankName: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_name_on_account')}</span><input className="input" value={props.draft.bankNameOnAccount} onInput={(e) => props.onUpdateDraft({ bankNameOnAccount: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_account_type')}</span><input className="input" value={props.draft.bankAccountType} onInput={(e) => props.onUpdateDraft({ bankAccountType: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_account_number')}</span><input className="input" value={props.draft.bankAccountNumber} onInput={(e) => props.onUpdateDraft({ bankAccountNumber: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_routing_number')}</span><input className="input" value={props.draft.bankRoutingNumber} onInput={(e) => props.onUpdateDraft({ bankRoutingNumber: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_branch_number')}</span><input className="input" value={props.draft.bankBranchNumber} onInput={(e) => props.onUpdateDraft({ bankBranchNumber: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_pin')}</span><input className="input" value={props.draft.bankPin} onInput={(e) => props.onUpdateDraft({ bankPin: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_swift_code')}</span><input className="input" value={props.draft.bankSwiftCode} onInput={(e) => props.onUpdateDraft({ bankSwiftCode: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_iban')}</span><input className="input" value={props.draft.bankIban} onInput={(e) => props.onUpdateDraft({ bankIban: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_bank_contact_phone')}</span><input className="input" value={props.draft.bankContactPhone} onInput={(e) => props.onUpdateDraft({ bankContactPhone: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.draft.type === 7 && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_drivers_license_details')}</h4>
|
||||
<div className="field-grid">
|
||||
<label className="field"><span>{t('txt_first_name')}</span><input className="input" value={props.draft.licenseFirstName} onInput={(e) => props.onUpdateDraft({ licenseFirstName: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_middle_name')}</span><input className="input" value={props.draft.licenseMiddleName} onInput={(e) => props.onUpdateDraft({ licenseMiddleName: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_last_name')}</span><input className="input" value={props.draft.licenseLastName} onInput={(e) => props.onUpdateDraft({ licenseLastName: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_date_of_birth')}</span><input className="input" value={props.draft.licenseDateOfBirth} onInput={(e) => props.onUpdateDraft({ licenseDateOfBirth: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_license_number')}</span><input className="input" value={props.draft.licenseNumber} onInput={(e) => props.onUpdateDraft({ licenseNumber: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issuing_country')}</span><input className="input" value={props.draft.licenseIssuingCountry} onInput={(e) => props.onUpdateDraft({ licenseIssuingCountry: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issuing_state')}</span><input className="input" value={props.draft.licenseIssuingState} onInput={(e) => props.onUpdateDraft({ licenseIssuingState: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issue_date')}</span><input className="input" value={props.draft.licenseIssueDate} onInput={(e) => props.onUpdateDraft({ licenseIssueDate: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_expiration_date')}</span><input className="input" value={props.draft.licenseExpirationDate} onInput={(e) => props.onUpdateDraft({ licenseExpirationDate: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issuing_authority')}</span><input className="input" value={props.draft.licenseIssuingAuthority} onInput={(e) => props.onUpdateDraft({ licenseIssuingAuthority: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_license_class')}</span><input className="input" value={props.draft.licenseClass} onInput={(e) => props.onUpdateDraft({ licenseClass: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.draft.type === 8 && (
|
||||
<div className="card">
|
||||
<h4>{t('txt_passport_details')}</h4>
|
||||
<div className="field-grid">
|
||||
<label className="field"><span>{t('txt_surname')}</span><input className="input" value={props.draft.passportSurname} onInput={(e) => props.onUpdateDraft({ passportSurname: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_given_name')}</span><input className="input" value={props.draft.passportGivenName} onInput={(e) => props.onUpdateDraft({ passportGivenName: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_date_of_birth')}</span><input className="input" value={props.draft.passportDateOfBirth} onInput={(e) => props.onUpdateDraft({ passportDateOfBirth: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_sex')}</span><input className="input" value={props.draft.passportSex} onInput={(e) => props.onUpdateDraft({ passportSex: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_birth_place')}</span><input className="input" value={props.draft.passportBirthPlace} onInput={(e) => props.onUpdateDraft({ passportBirthPlace: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_nationality')}</span><input className="input" value={props.draft.passportNationality} onInput={(e) => props.onUpdateDraft({ passportNationality: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issuing_country')}</span><input className="input" value={props.draft.passportIssuingCountry} onInput={(e) => props.onUpdateDraft({ passportIssuingCountry: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_passport_number')}</span><input className="input" value={props.draft.passportNumber} onInput={(e) => props.onUpdateDraft({ passportNumber: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_passport_type')}</span><input className="input" value={props.draft.passportType} onInput={(e) => props.onUpdateDraft({ passportType: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_national_id_number')}</span><input className="input" value={props.draft.passportNationalIdentificationNumber} onInput={(e) => props.onUpdateDraft({ passportNationalIdentificationNumber: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issuing_authority')}</span><input className="input" value={props.draft.passportIssuingAuthority} onInput={(e) => props.onUpdateDraft({ passportIssuingAuthority: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_issue_date')}</span><input className="input" value={props.draft.passportIssueDate} onInput={(e) => props.onUpdateDraft({ passportIssueDate: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
<label className="field"><span>{t('txt_expiration_date')}</span><input className="input" value={props.draft.passportExpirationDate} onInput={(e) => props.onUpdateDraft({ passportExpirationDate: (e.currentTarget as HTMLInputElement).value })} /></label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="card">
|
||||
<div className="section-head attachment-head">
|
||||
<h4>{t('txt_attachments')}</h4>
|
||||
|
||||
@@ -117,9 +117,18 @@ export default function VaultSidebar(props: VaultSidebarProps) {
|
||||
<button type="button" className={`tree-btn ${props.sidebarFilter.kind === 'type' && props.sidebarFilter.value === 'card' ? 'active' : ''}`} onClick={() => props.onChangeFilter({ kind: 'type', value: 'card' })}>
|
||||
<CreditCard size={14} className="tree-icon" /> <span className="tree-label">{t('txt_card')}</span>
|
||||
</button>
|
||||
<button type="button" className={`tree-btn ${props.sidebarFilter.kind === 'type' && props.sidebarFilter.value === 'bank' ? 'active' : ''}`} onClick={() => props.onChangeFilter({ kind: 'type', value: 'bank' })}>
|
||||
<CreditCard size={14} className="tree-icon" /> <span className="tree-label">{t('txt_bank_account')}</span>
|
||||
</button>
|
||||
<button type="button" className={`tree-btn ${props.sidebarFilter.kind === 'type' && props.sidebarFilter.value === 'identity' ? 'active' : ''}`} onClick={() => props.onChangeFilter({ kind: 'type', value: 'identity' })}>
|
||||
<ShieldUser size={14} className="tree-icon" /> <span className="tree-label">{t('txt_identity')}</span>
|
||||
</button>
|
||||
<button type="button" className={`tree-btn ${props.sidebarFilter.kind === 'type' && props.sidebarFilter.value === 'license' ? 'active' : ''}`} onClick={() => props.onChangeFilter({ kind: 'type', value: 'license' })}>
|
||||
<ShieldUser size={14} className="tree-icon" /> <span className="tree-label">{t('txt_drivers_license')}</span>
|
||||
</button>
|
||||
<button type="button" className={`tree-btn ${props.sidebarFilter.kind === 'type' && props.sidebarFilter.value === 'passport' ? 'active' : ''}`} onClick={() => props.onChangeFilter({ kind: 'type', value: 'passport' })}>
|
||||
<KeyRound size={14} className="tree-icon" /> <span className="tree-label">{t('txt_passport')}</span>
|
||||
</button>
|
||||
<button type="button" className={`tree-btn ${props.sidebarFilter.kind === 'type' && props.sidebarFilter.value === 'note' ? 'active' : ''}`} onClick={() => props.onChangeFilter({ kind: 'type', value: 'note' })}>
|
||||
<StickyNote size={14} className="tree-icon" /> <span className="tree-label">{t('txt_note')}</span>
|
||||
</button>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { firstCipherUri, hostFromUri, websiteIconUrl } from '@/lib/website-utils
|
||||
import { normalizeEquivalentDomain } from '@shared/domain-normalize';
|
||||
import WebsiteIcon from './WebsiteIcon';
|
||||
|
||||
export type TypeFilter = 'login' | 'card' | 'identity' | 'note' | 'ssh';
|
||||
export type TypeFilter = 'login' | 'card' | 'identity' | 'note' | 'ssh' | 'bank' | 'license' | 'passport';
|
||||
export type VaultSortMode = 'edited' | 'created' | 'name';
|
||||
export type DuplicateDetectionMode = 'exact' | 'login-site' | 'login-credentials' | 'password';
|
||||
export type SidebarFilter =
|
||||
@@ -98,6 +98,32 @@ export function cardListSubtitle(cipher: Cipher): string {
|
||||
return cipherTypeLabel(3);
|
||||
}
|
||||
|
||||
export function bankAccountListSubtitle(cipher: Cipher): string {
|
||||
const bankName = valueOrFallback(cipher.bankAccount?.decBankName ?? cipher.bankAccount?.bankName).trim();
|
||||
const accountType = valueOrFallback(cipher.bankAccount?.decAccountType ?? cipher.bankAccount?.accountType).trim();
|
||||
const accountNumber = valueOrFallback(cipher.bankAccount?.decAccountNumber ?? cipher.bankAccount?.accountNumber).replace(/\D/g, '');
|
||||
const last4 = accountNumber.length >= 4 ? accountNumber.slice(-4) : '';
|
||||
return [bankName, accountType, last4 ? `*${last4}` : ''].filter(Boolean).join(', ') || cipherTypeLabel(6);
|
||||
}
|
||||
|
||||
export function driversLicenseListSubtitle(cipher: Cipher): string {
|
||||
const licenseNumber = valueOrFallback(cipher.driversLicense?.decLicenseNumber ?? cipher.driversLicense?.licenseNumber).trim();
|
||||
const name = [
|
||||
valueOrFallback(cipher.driversLicense?.decFirstName ?? cipher.driversLicense?.firstName).trim(),
|
||||
valueOrFallback(cipher.driversLicense?.decLastName ?? cipher.driversLicense?.lastName).trim(),
|
||||
].filter(Boolean).join(' ');
|
||||
return licenseNumber || name || cipherTypeLabel(7);
|
||||
}
|
||||
|
||||
export function passportListSubtitle(cipher: Cipher): string {
|
||||
const passportNumber = valueOrFallback(cipher.passport?.decPassportNumber ?? cipher.passport?.passportNumber).trim();
|
||||
const name = [
|
||||
valueOrFallback(cipher.passport?.decGivenName ?? cipher.passport?.givenName).trim(),
|
||||
valueOrFallback(cipher.passport?.decSurname ?? cipher.passport?.surname).trim(),
|
||||
].filter(Boolean).join(' ');
|
||||
return passportNumber || name || cipherTypeLabel(8);
|
||||
}
|
||||
|
||||
export function CardBrandIcon({ brand }: { brand?: string | null }) {
|
||||
const display = displayCardBrand(brand);
|
||||
const key = display.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'generic';
|
||||
@@ -118,7 +144,10 @@ export function getCreateTypeOptions(): TypeOption[] {
|
||||
return [
|
||||
{ type: 1, label: t('txt_login') },
|
||||
{ type: 3, label: t('txt_card') },
|
||||
{ type: 6, label: t('txt_bank_account') },
|
||||
{ type: 4, label: t('txt_identity') },
|
||||
{ type: 7, label: t('txt_drivers_license') },
|
||||
{ type: 8, label: t('txt_passport') },
|
||||
{ type: 2, label: t('txt_note') },
|
||||
{ type: 5, label: t('txt_ssh_key') },
|
||||
];
|
||||
@@ -185,6 +214,9 @@ export function CreateTypeIcon({ type }: { type: number }) {
|
||||
if (type === 4) return <ShieldUser size={15} />;
|
||||
if (type === 2) return <StickyNote size={15} />;
|
||||
if (type === 5) return <KeyRound size={15} />;
|
||||
if (type === 6) return <CreditCard size={15} />;
|
||||
if (type === 7) return <ShieldUser size={15} />;
|
||||
if (type === 8) return <FileKey2 size={15} />;
|
||||
return <FileKey2 size={15} />;
|
||||
}
|
||||
|
||||
@@ -193,7 +225,11 @@ export function cipherTypeKey(type: number): TypeFilter {
|
||||
if (type === 3) return 'card';
|
||||
if (type === 4) return 'identity';
|
||||
if (type === 2) return 'note';
|
||||
return 'ssh';
|
||||
if (type === 5) return 'ssh';
|
||||
if (type === 6) return 'bank';
|
||||
if (type === 7) return 'license';
|
||||
if (type === 8) return 'passport';
|
||||
return 'note';
|
||||
}
|
||||
|
||||
function cipherDeletedValue(cipher: Cipher): boolean {
|
||||
@@ -230,6 +266,9 @@ export function cipherTypeLabel(type: number): string {
|
||||
if (type === 4) return t('txt_identity');
|
||||
if (type === 2) return t('txt_secure_note');
|
||||
if (type === 5) return t('txt_ssh_key');
|
||||
if (type === 6) return t('txt_bank_account');
|
||||
if (type === 7) return t('txt_drivers_license');
|
||||
if (type === 8) return t('txt_passport');
|
||||
return t('txt_item');
|
||||
}
|
||||
|
||||
@@ -239,6 +278,9 @@ export function TypeIcon({ type }: { type: number }) {
|
||||
if (type === 4) return <ShieldUser size={18} />;
|
||||
if (type === 2) return <StickyNote size={18} />;
|
||||
if (type === 5) return <KeyRound size={18} />;
|
||||
if (type === 6) return <CreditCard size={18} />;
|
||||
if (type === 7) return <ShieldUser size={18} />;
|
||||
if (type === 8) return <FileKey2 size={18} />;
|
||||
return <FileKey2 size={18} />;
|
||||
}
|
||||
|
||||
@@ -355,6 +397,52 @@ export function buildCipherDuplicateSignature(cipher: Cipher): string {
|
||||
fingerprint: valueOrFallback(cipher.sshKey.decFingerprint ?? cipher.sshKey.keyFingerprint ?? cipher.sshKey.fingerprint),
|
||||
}
|
||||
: null,
|
||||
bankAccount: cipher.bankAccount
|
||||
? {
|
||||
bankName: valueOrFallback(cipher.bankAccount.decBankName ?? cipher.bankAccount.bankName),
|
||||
nameOnAccount: valueOrFallback(cipher.bankAccount.decNameOnAccount ?? cipher.bankAccount.nameOnAccount),
|
||||
accountType: valueOrFallback(cipher.bankAccount.decAccountType ?? cipher.bankAccount.accountType),
|
||||
accountNumber: valueOrFallback(cipher.bankAccount.decAccountNumber ?? cipher.bankAccount.accountNumber),
|
||||
routingNumber: valueOrFallback(cipher.bankAccount.decRoutingNumber ?? cipher.bankAccount.routingNumber),
|
||||
branchNumber: valueOrFallback(cipher.bankAccount.decBranchNumber ?? cipher.bankAccount.branchNumber),
|
||||
pin: valueOrFallback(cipher.bankAccount.decPin ?? cipher.bankAccount.pin),
|
||||
swiftCode: valueOrFallback(cipher.bankAccount.decSwiftCode ?? cipher.bankAccount.swiftCode),
|
||||
iban: valueOrFallback(cipher.bankAccount.decIban ?? cipher.bankAccount.iban),
|
||||
bankContactPhone: valueOrFallback(cipher.bankAccount.decBankContactPhone ?? cipher.bankAccount.bankContactPhone),
|
||||
}
|
||||
: null,
|
||||
driversLicense: cipher.driversLicense
|
||||
? {
|
||||
firstName: valueOrFallback(cipher.driversLicense.decFirstName ?? cipher.driversLicense.firstName),
|
||||
middleName: valueOrFallback(cipher.driversLicense.decMiddleName ?? cipher.driversLicense.middleName),
|
||||
lastName: valueOrFallback(cipher.driversLicense.decLastName ?? cipher.driversLicense.lastName),
|
||||
dateOfBirth: valueOrFallback(cipher.driversLicense.decDateOfBirth ?? cipher.driversLicense.dateOfBirth),
|
||||
licenseNumber: valueOrFallback(cipher.driversLicense.decLicenseNumber ?? cipher.driversLicense.licenseNumber),
|
||||
issuingCountry: valueOrFallback(cipher.driversLicense.decIssuingCountry ?? cipher.driversLicense.issuingCountry),
|
||||
issuingState: valueOrFallback(cipher.driversLicense.decIssuingState ?? cipher.driversLicense.issuingState),
|
||||
issueDate: valueOrFallback(cipher.driversLicense.decIssueDate ?? cipher.driversLicense.issueDate),
|
||||
expirationDate: valueOrFallback(cipher.driversLicense.decExpirationDate ?? cipher.driversLicense.expirationDate),
|
||||
issuingAuthority: valueOrFallback(cipher.driversLicense.decIssuingAuthority ?? cipher.driversLicense.issuingAuthority),
|
||||
licenseClass: valueOrFallback(cipher.driversLicense.decLicenseClass ?? cipher.driversLicense.licenseClass),
|
||||
}
|
||||
: null,
|
||||
passport: cipher.passport
|
||||
? {
|
||||
surname: valueOrFallback(cipher.passport.decSurname ?? cipher.passport.surname),
|
||||
givenName: valueOrFallback(cipher.passport.decGivenName ?? cipher.passport.givenName),
|
||||
dateOfBirth: valueOrFallback(cipher.passport.decDateOfBirth ?? cipher.passport.dateOfBirth),
|
||||
sex: valueOrFallback(cipher.passport.decSex ?? cipher.passport.sex),
|
||||
birthPlace: valueOrFallback(cipher.passport.decBirthPlace ?? cipher.passport.birthPlace),
|
||||
nationality: valueOrFallback(cipher.passport.decNationality ?? cipher.passport.nationality),
|
||||
issuingCountry: valueOrFallback(cipher.passport.decIssuingCountry ?? cipher.passport.issuingCountry),
|
||||
passportNumber: valueOrFallback(cipher.passport.decPassportNumber ?? cipher.passport.passportNumber),
|
||||
passportType: valueOrFallback(cipher.passport.decPassportType ?? cipher.passport.passportType),
|
||||
nationalIdentificationNumber: valueOrFallback(cipher.passport.decNationalIdentificationNumber ?? cipher.passport.nationalIdentificationNumber),
|
||||
issuingAuthority: valueOrFallback(cipher.passport.decIssuingAuthority ?? cipher.passport.issuingAuthority),
|
||||
issueDate: valueOrFallback(cipher.passport.decIssueDate ?? cipher.passport.issueDate),
|
||||
expirationDate: valueOrFallback(cipher.passport.decExpirationDate ?? cipher.passport.expirationDate),
|
||||
}
|
||||
: null,
|
||||
secureNoteType: cipher.secureNote?.type ?? null,
|
||||
fields: (cipher.fields || []).map((field) => ({
|
||||
type: field.type ?? null,
|
||||
@@ -427,6 +515,40 @@ export function createEmptyDraft(type: number): VaultDraft {
|
||||
sshPrivateKey: '',
|
||||
sshPublicKey: '',
|
||||
sshFingerprint: '',
|
||||
bankName: '',
|
||||
bankNameOnAccount: '',
|
||||
bankAccountType: '',
|
||||
bankAccountNumber: '',
|
||||
bankRoutingNumber: '',
|
||||
bankBranchNumber: '',
|
||||
bankPin: '',
|
||||
bankSwiftCode: '',
|
||||
bankIban: '',
|
||||
bankContactPhone: '',
|
||||
licenseFirstName: '',
|
||||
licenseMiddleName: '',
|
||||
licenseLastName: '',
|
||||
licenseDateOfBirth: '',
|
||||
licenseNumber: '',
|
||||
licenseIssuingCountry: '',
|
||||
licenseIssuingState: '',
|
||||
licenseIssueDate: '',
|
||||
licenseExpirationDate: '',
|
||||
licenseIssuingAuthority: '',
|
||||
licenseClass: '',
|
||||
passportSurname: '',
|
||||
passportGivenName: '',
|
||||
passportDateOfBirth: '',
|
||||
passportSex: '',
|
||||
passportBirthPlace: '',
|
||||
passportNationality: '',
|
||||
passportIssuingCountry: '',
|
||||
passportNumber: '',
|
||||
passportType: '',
|
||||
passportNationalIdentificationNumber: '',
|
||||
passportIssuingAuthority: '',
|
||||
passportIssueDate: '',
|
||||
passportExpirationDate: '',
|
||||
customFields: [],
|
||||
};
|
||||
}
|
||||
@@ -490,6 +612,46 @@ export function draftFromCipher(cipher: Cipher): VaultDraft {
|
||||
draft.sshPublicKey = cipher.sshKey.decPublicKey || '';
|
||||
draft.sshFingerprint = cipher.sshKey.decFingerprint || '';
|
||||
}
|
||||
if (cipher.bankAccount) {
|
||||
draft.bankName = cipher.bankAccount.decBankName || '';
|
||||
draft.bankNameOnAccount = cipher.bankAccount.decNameOnAccount || '';
|
||||
draft.bankAccountType = cipher.bankAccount.decAccountType || '';
|
||||
draft.bankAccountNumber = cipher.bankAccount.decAccountNumber || '';
|
||||
draft.bankRoutingNumber = cipher.bankAccount.decRoutingNumber || '';
|
||||
draft.bankBranchNumber = cipher.bankAccount.decBranchNumber || '';
|
||||
draft.bankPin = cipher.bankAccount.decPin || '';
|
||||
draft.bankSwiftCode = cipher.bankAccount.decSwiftCode || '';
|
||||
draft.bankIban = cipher.bankAccount.decIban || '';
|
||||
draft.bankContactPhone = cipher.bankAccount.decBankContactPhone || '';
|
||||
}
|
||||
if (cipher.driversLicense) {
|
||||
draft.licenseFirstName = cipher.driversLicense.decFirstName || '';
|
||||
draft.licenseMiddleName = cipher.driversLicense.decMiddleName || '';
|
||||
draft.licenseLastName = cipher.driversLicense.decLastName || '';
|
||||
draft.licenseDateOfBirth = cipher.driversLicense.decDateOfBirth || '';
|
||||
draft.licenseNumber = cipher.driversLicense.decLicenseNumber || '';
|
||||
draft.licenseIssuingCountry = cipher.driversLicense.decIssuingCountry || '';
|
||||
draft.licenseIssuingState = cipher.driversLicense.decIssuingState || '';
|
||||
draft.licenseIssueDate = cipher.driversLicense.decIssueDate || '';
|
||||
draft.licenseExpirationDate = cipher.driversLicense.decExpirationDate || '';
|
||||
draft.licenseIssuingAuthority = cipher.driversLicense.decIssuingAuthority || '';
|
||||
draft.licenseClass = cipher.driversLicense.decLicenseClass || '';
|
||||
}
|
||||
if (cipher.passport) {
|
||||
draft.passportSurname = cipher.passport.decSurname || '';
|
||||
draft.passportGivenName = cipher.passport.decGivenName || '';
|
||||
draft.passportDateOfBirth = cipher.passport.decDateOfBirth || '';
|
||||
draft.passportSex = cipher.passport.decSex || '';
|
||||
draft.passportBirthPlace = cipher.passport.decBirthPlace || '';
|
||||
draft.passportNationality = cipher.passport.decNationality || '';
|
||||
draft.passportIssuingCountry = cipher.passport.decIssuingCountry || '';
|
||||
draft.passportNumber = cipher.passport.decPassportNumber || '';
|
||||
draft.passportType = cipher.passport.decPassportType || '';
|
||||
draft.passportNationalIdentificationNumber = cipher.passport.decNationalIdentificationNumber || '';
|
||||
draft.passportIssuingAuthority = cipher.passport.decIssuingAuthority || '';
|
||||
draft.passportIssueDate = cipher.passport.decIssueDate || '';
|
||||
draft.passportExpirationDate = cipher.passport.decExpirationDate || '';
|
||||
}
|
||||
draft.customFields = (cipher.fields || []).map((field) => ({
|
||||
type: parseFieldType(field.type),
|
||||
label: field.decName || '',
|
||||
|
||||
@@ -513,6 +513,30 @@ async function encryptTextValue(value: string, enc: Uint8Array, mac: Uint8Array)
|
||||
return encryptBw(new TextEncoder().encode(s), enc, mac);
|
||||
}
|
||||
|
||||
function stripDecodedObjectFields(value: unknown): Record<string, unknown> {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const [key, item] of Object.entries(value)) {
|
||||
if (/^dec[A-Z]/.test(key)) continue;
|
||||
out[key] = item;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function encryptObjectFields(
|
||||
existing: unknown,
|
||||
entries: Array<[string, string]>,
|
||||
draft: VaultDraft,
|
||||
enc: Uint8Array,
|
||||
mac: Uint8Array
|
||||
): Promise<Record<string, unknown>> {
|
||||
const out = stripDecodedObjectFields(existing);
|
||||
for (const [fieldName, draftKey] of entries) {
|
||||
out[fieldName] = await encryptTextValue(String((draft as unknown as Record<string, unknown>)[draftKey] || ''), enc, mac);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function encryptPasswordHistory(
|
||||
entries: CipherPasswordHistoryEntry[] | null | undefined,
|
||||
enc: Uint8Array,
|
||||
@@ -587,6 +611,40 @@ function draftFromDecryptedCipher(cipher: Cipher): VaultDraft {
|
||||
sshPrivateKey: '',
|
||||
sshPublicKey: '',
|
||||
sshFingerprint: '',
|
||||
bankName: '',
|
||||
bankNameOnAccount: '',
|
||||
bankAccountType: '',
|
||||
bankAccountNumber: '',
|
||||
bankRoutingNumber: '',
|
||||
bankBranchNumber: '',
|
||||
bankPin: '',
|
||||
bankSwiftCode: '',
|
||||
bankIban: '',
|
||||
bankContactPhone: '',
|
||||
licenseFirstName: '',
|
||||
licenseMiddleName: '',
|
||||
licenseLastName: '',
|
||||
licenseDateOfBirth: '',
|
||||
licenseNumber: '',
|
||||
licenseIssuingCountry: '',
|
||||
licenseIssuingState: '',
|
||||
licenseIssueDate: '',
|
||||
licenseExpirationDate: '',
|
||||
licenseIssuingAuthority: '',
|
||||
licenseClass: '',
|
||||
passportSurname: '',
|
||||
passportGivenName: '',
|
||||
passportDateOfBirth: '',
|
||||
passportSex: '',
|
||||
passportBirthPlace: '',
|
||||
passportNationality: '',
|
||||
passportIssuingCountry: '',
|
||||
passportNumber: '',
|
||||
passportType: '',
|
||||
passportNationalIdentificationNumber: '',
|
||||
passportIssuingAuthority: '',
|
||||
passportIssueDate: '',
|
||||
passportExpirationDate: '',
|
||||
customFields: [],
|
||||
};
|
||||
|
||||
@@ -662,6 +720,43 @@ function draftFromDecryptedCipher(cipher: Cipher): VaultDraft {
|
||||
cipher.sshKey.decFingerprint,
|
||||
cipher.sshKey.keyFingerprint || cipher.sshKey.fingerprint
|
||||
);
|
||||
} else if (type === 6 && cipher.bankAccount) {
|
||||
draft.bankName = plainCipherValue(cipher.bankAccount.decBankName, cipher.bankAccount.bankName);
|
||||
draft.bankNameOnAccount = plainCipherValue(cipher.bankAccount.decNameOnAccount, cipher.bankAccount.nameOnAccount);
|
||||
draft.bankAccountType = plainCipherValue(cipher.bankAccount.decAccountType, cipher.bankAccount.accountType);
|
||||
draft.bankAccountNumber = plainCipherValue(cipher.bankAccount.decAccountNumber, cipher.bankAccount.accountNumber);
|
||||
draft.bankRoutingNumber = plainCipherValue(cipher.bankAccount.decRoutingNumber, cipher.bankAccount.routingNumber);
|
||||
draft.bankBranchNumber = plainCipherValue(cipher.bankAccount.decBranchNumber, cipher.bankAccount.branchNumber);
|
||||
draft.bankPin = plainCipherValue(cipher.bankAccount.decPin, cipher.bankAccount.pin);
|
||||
draft.bankSwiftCode = plainCipherValue(cipher.bankAccount.decSwiftCode, cipher.bankAccount.swiftCode);
|
||||
draft.bankIban = plainCipherValue(cipher.bankAccount.decIban, cipher.bankAccount.iban);
|
||||
draft.bankContactPhone = plainCipherValue(cipher.bankAccount.decBankContactPhone, cipher.bankAccount.bankContactPhone);
|
||||
} else if (type === 7 && cipher.driversLicense) {
|
||||
draft.licenseFirstName = plainCipherValue(cipher.driversLicense.decFirstName, cipher.driversLicense.firstName);
|
||||
draft.licenseMiddleName = plainCipherValue(cipher.driversLicense.decMiddleName, cipher.driversLicense.middleName);
|
||||
draft.licenseLastName = plainCipherValue(cipher.driversLicense.decLastName, cipher.driversLicense.lastName);
|
||||
draft.licenseDateOfBirth = plainCipherValue(cipher.driversLicense.decDateOfBirth, cipher.driversLicense.dateOfBirth);
|
||||
draft.licenseNumber = plainCipherValue(cipher.driversLicense.decLicenseNumber, cipher.driversLicense.licenseNumber);
|
||||
draft.licenseIssuingCountry = plainCipherValue(cipher.driversLicense.decIssuingCountry, cipher.driversLicense.issuingCountry);
|
||||
draft.licenseIssuingState = plainCipherValue(cipher.driversLicense.decIssuingState, cipher.driversLicense.issuingState);
|
||||
draft.licenseIssueDate = plainCipherValue(cipher.driversLicense.decIssueDate, cipher.driversLicense.issueDate);
|
||||
draft.licenseExpirationDate = plainCipherValue(cipher.driversLicense.decExpirationDate, cipher.driversLicense.expirationDate);
|
||||
draft.licenseIssuingAuthority = plainCipherValue(cipher.driversLicense.decIssuingAuthority, cipher.driversLicense.issuingAuthority);
|
||||
draft.licenseClass = plainCipherValue(cipher.driversLicense.decLicenseClass, cipher.driversLicense.licenseClass);
|
||||
} else if (type === 8 && cipher.passport) {
|
||||
draft.passportSurname = plainCipherValue(cipher.passport.decSurname, cipher.passport.surname);
|
||||
draft.passportGivenName = plainCipherValue(cipher.passport.decGivenName, cipher.passport.givenName);
|
||||
draft.passportDateOfBirth = plainCipherValue(cipher.passport.decDateOfBirth, cipher.passport.dateOfBirth);
|
||||
draft.passportSex = plainCipherValue(cipher.passport.decSex, cipher.passport.sex);
|
||||
draft.passportBirthPlace = plainCipherValue(cipher.passport.decBirthPlace, cipher.passport.birthPlace);
|
||||
draft.passportNationality = plainCipherValue(cipher.passport.decNationality, cipher.passport.nationality);
|
||||
draft.passportIssuingCountry = plainCipherValue(cipher.passport.decIssuingCountry, cipher.passport.issuingCountry);
|
||||
draft.passportNumber = plainCipherValue(cipher.passport.decPassportNumber, cipher.passport.passportNumber);
|
||||
draft.passportType = plainCipherValue(cipher.passport.decPassportType, cipher.passport.passportType);
|
||||
draft.passportNationalIdentificationNumber = plainCipherValue(cipher.passport.decNationalIdentificationNumber, cipher.passport.nationalIdentificationNumber);
|
||||
draft.passportIssuingAuthority = plainCipherValue(cipher.passport.decIssuingAuthority, cipher.passport.issuingAuthority);
|
||||
draft.passportIssueDate = plainCipherValue(cipher.passport.decIssueDate, cipher.passport.issueDate);
|
||||
draft.passportExpirationDate = plainCipherValue(cipher.passport.decExpirationDate, cipher.passport.expirationDate);
|
||||
}
|
||||
|
||||
return draft;
|
||||
@@ -983,6 +1078,10 @@ function getCipherKeyMismatchProbes(cipher: Cipher): string[] {
|
||||
cipher.identity?.title,
|
||||
cipher.identity?.firstName,
|
||||
cipher.sshKey?.privateKey,
|
||||
cipher.bankAccount?.bankName,
|
||||
cipher.bankAccount?.accountNumber,
|
||||
cipher.driversLicense?.licenseNumber,
|
||||
cipher.passport?.passportNumber,
|
||||
...(cipher.fields || []).flatMap((field) => [field.name, field.value]),
|
||||
];
|
||||
const probes: string[] = [];
|
||||
@@ -1053,6 +1152,40 @@ function hasUnresolvedEncryptedFields(cipher: Cipher): boolean {
|
||||
[cipher.sshKey?.privateKey, cipher.sshKey?.decPrivateKey],
|
||||
[cipher.sshKey?.publicKey, cipher.sshKey?.decPublicKey],
|
||||
[cipher.sshKey?.keyFingerprint || cipher.sshKey?.fingerprint, cipher.sshKey?.decFingerprint],
|
||||
[cipher.bankAccount?.bankName, cipher.bankAccount?.decBankName],
|
||||
[cipher.bankAccount?.nameOnAccount, cipher.bankAccount?.decNameOnAccount],
|
||||
[cipher.bankAccount?.accountType, cipher.bankAccount?.decAccountType],
|
||||
[cipher.bankAccount?.accountNumber, cipher.bankAccount?.decAccountNumber],
|
||||
[cipher.bankAccount?.routingNumber, cipher.bankAccount?.decRoutingNumber],
|
||||
[cipher.bankAccount?.branchNumber, cipher.bankAccount?.decBranchNumber],
|
||||
[cipher.bankAccount?.pin, cipher.bankAccount?.decPin],
|
||||
[cipher.bankAccount?.swiftCode, cipher.bankAccount?.decSwiftCode],
|
||||
[cipher.bankAccount?.iban, cipher.bankAccount?.decIban],
|
||||
[cipher.bankAccount?.bankContactPhone, cipher.bankAccount?.decBankContactPhone],
|
||||
[cipher.driversLicense?.firstName, cipher.driversLicense?.decFirstName],
|
||||
[cipher.driversLicense?.middleName, cipher.driversLicense?.decMiddleName],
|
||||
[cipher.driversLicense?.lastName, cipher.driversLicense?.decLastName],
|
||||
[cipher.driversLicense?.dateOfBirth, cipher.driversLicense?.decDateOfBirth],
|
||||
[cipher.driversLicense?.licenseNumber, cipher.driversLicense?.decLicenseNumber],
|
||||
[cipher.driversLicense?.issuingCountry, cipher.driversLicense?.decIssuingCountry],
|
||||
[cipher.driversLicense?.issuingState, cipher.driversLicense?.decIssuingState],
|
||||
[cipher.driversLicense?.issueDate, cipher.driversLicense?.decIssueDate],
|
||||
[cipher.driversLicense?.expirationDate, cipher.driversLicense?.decExpirationDate],
|
||||
[cipher.driversLicense?.issuingAuthority, cipher.driversLicense?.decIssuingAuthority],
|
||||
[cipher.driversLicense?.licenseClass, cipher.driversLicense?.decLicenseClass],
|
||||
[cipher.passport?.surname, cipher.passport?.decSurname],
|
||||
[cipher.passport?.givenName, cipher.passport?.decGivenName],
|
||||
[cipher.passport?.dateOfBirth, cipher.passport?.decDateOfBirth],
|
||||
[cipher.passport?.sex, cipher.passport?.decSex],
|
||||
[cipher.passport?.birthPlace, cipher.passport?.decBirthPlace],
|
||||
[cipher.passport?.nationality, cipher.passport?.decNationality],
|
||||
[cipher.passport?.issuingCountry, cipher.passport?.decIssuingCountry],
|
||||
[cipher.passport?.passportNumber, cipher.passport?.decPassportNumber],
|
||||
[cipher.passport?.passportType, cipher.passport?.decPassportType],
|
||||
[cipher.passport?.nationalIdentificationNumber, cipher.passport?.decNationalIdentificationNumber],
|
||||
[cipher.passport?.issuingAuthority, cipher.passport?.decIssuingAuthority],
|
||||
[cipher.passport?.issueDate, cipher.passport?.decIssueDate],
|
||||
[cipher.passport?.expirationDate, cipher.passport?.decExpirationDate],
|
||||
...(cipher.fields || []).flatMap((field) => [
|
||||
[field.name, field.decName] as [unknown, unknown],
|
||||
[field.value, field.decValue] as [unknown, unknown],
|
||||
@@ -1157,6 +1290,9 @@ async function buildCipherPayload(
|
||||
identity: null,
|
||||
secureNote: null,
|
||||
sshKey: null,
|
||||
bankAccount: null,
|
||||
driversLicense: null,
|
||||
passport: null,
|
||||
fields: await encryptCustomFields(draft.customFields || [], keys.enc, keys.mac),
|
||||
passwordHistory: await encryptPasswordHistory(cipher?.passwordHistory, keys.enc, keys.mac),
|
||||
};
|
||||
@@ -1222,11 +1358,73 @@ async function buildCipherPayload(
|
||||
} else if (type === 5) {
|
||||
const encryptedFingerprint = await encryptTextValue(draft.sshFingerprint, keys.enc, keys.mac);
|
||||
payload.sshKey = {
|
||||
...stripDecodedObjectFields(cipher?.sshKey),
|
||||
privateKey: await encryptTextValue(draft.sshPrivateKey, keys.enc, keys.mac),
|
||||
publicKey: await encryptTextValue(draft.sshPublicKey, keys.enc, keys.mac),
|
||||
keyFingerprint: encryptedFingerprint,
|
||||
fingerprint: encryptedFingerprint,
|
||||
};
|
||||
} else if (type === 6) {
|
||||
payload.bankAccount = await encryptObjectFields(
|
||||
cipher?.bankAccount,
|
||||
[
|
||||
['bankName', 'bankName'],
|
||||
['nameOnAccount', 'bankNameOnAccount'],
|
||||
['accountType', 'bankAccountType'],
|
||||
['accountNumber', 'bankAccountNumber'],
|
||||
['routingNumber', 'bankRoutingNumber'],
|
||||
['branchNumber', 'bankBranchNumber'],
|
||||
['pin', 'bankPin'],
|
||||
['swiftCode', 'bankSwiftCode'],
|
||||
['iban', 'bankIban'],
|
||||
['bankContactPhone', 'bankContactPhone'],
|
||||
],
|
||||
draft,
|
||||
keys.enc,
|
||||
keys.mac
|
||||
);
|
||||
} else if (type === 7) {
|
||||
payload.driversLicense = await encryptObjectFields(
|
||||
cipher?.driversLicense,
|
||||
[
|
||||
['firstName', 'licenseFirstName'],
|
||||
['middleName', 'licenseMiddleName'],
|
||||
['lastName', 'licenseLastName'],
|
||||
['dateOfBirth', 'licenseDateOfBirth'],
|
||||
['licenseNumber', 'licenseNumber'],
|
||||
['issuingCountry', 'licenseIssuingCountry'],
|
||||
['issuingState', 'licenseIssuingState'],
|
||||
['issueDate', 'licenseIssueDate'],
|
||||
['expirationDate', 'licenseExpirationDate'],
|
||||
['issuingAuthority', 'licenseIssuingAuthority'],
|
||||
['licenseClass', 'licenseClass'],
|
||||
],
|
||||
draft,
|
||||
keys.enc,
|
||||
keys.mac
|
||||
);
|
||||
} else if (type === 8) {
|
||||
payload.passport = await encryptObjectFields(
|
||||
cipher?.passport,
|
||||
[
|
||||
['surname', 'passportSurname'],
|
||||
['givenName', 'passportGivenName'],
|
||||
['dateOfBirth', 'passportDateOfBirth'],
|
||||
['sex', 'passportSex'],
|
||||
['birthPlace', 'passportBirthPlace'],
|
||||
['nationality', 'passportNationality'],
|
||||
['issuingCountry', 'passportIssuingCountry'],
|
||||
['passportNumber', 'passportNumber'],
|
||||
['passportType', 'passportType'],
|
||||
['nationalIdentificationNumber', 'passportNationalIdentificationNumber'],
|
||||
['issuingAuthority', 'passportIssuingAuthority'],
|
||||
['issueDate', 'passportIssueDate'],
|
||||
['expirationDate', 'passportExpirationDate'],
|
||||
],
|
||||
draft,
|
||||
keys.enc,
|
||||
keys.mac
|
||||
);
|
||||
} else if (type === 2) {
|
||||
payload.secureNote = { type: 0 };
|
||||
}
|
||||
|
||||
@@ -215,13 +215,13 @@ function mapCipherEncrypted(cipher: Cipher): Record<string, unknown> {
|
||||
const login = cipher.login;
|
||||
out.login = login
|
||||
? {
|
||||
...cloneValue(login),
|
||||
...(cloneWithoutDecodedFields(login) || {}),
|
||||
username: login.username ?? null,
|
||||
password: login.password ?? null,
|
||||
totp: login.totp ?? null,
|
||||
uris: Array.isArray(login.uris)
|
||||
? login.uris.map((uri) => ({
|
||||
...cloneValue(uri),
|
||||
...(cloneWithoutDecodedFields(uri) || {}),
|
||||
uri: uri?.uri ?? null,
|
||||
uriChecksum: uri?.uriChecksum ?? null,
|
||||
match: (uri as { match?: unknown })?.match ?? null,
|
||||
@@ -280,6 +280,7 @@ function mapCipherEncrypted(cipher: Cipher): Record<string, unknown> {
|
||||
|
||||
out.sshKey = cipher.sshKey
|
||||
? {
|
||||
...(cloneWithoutDecodedFields(cipher.sshKey) || {}),
|
||||
privateKey: cipher.sshKey.privateKey ?? null,
|
||||
publicKey: cipher.sshKey.publicKey ?? null,
|
||||
keyFingerprint: cipher.sshKey.keyFingerprint ?? cipher.sshKey.fingerprint ?? null,
|
||||
@@ -287,6 +288,9 @@ function mapCipherEncrypted(cipher: Cipher): Record<string, unknown> {
|
||||
fingerprint: cipher.sshKey.keyFingerprint ?? cipher.sshKey.fingerprint ?? null,
|
||||
}
|
||||
: null;
|
||||
out.bankAccount = cloneWithoutDecodedFields(cipher.bankAccount) ?? null;
|
||||
out.driversLicense = cloneWithoutDecodedFields(cipher.driversLicense) ?? null;
|
||||
out.passport = cloneWithoutDecodedFields(cipher.passport) ?? null;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -331,8 +335,8 @@ async function mapCipherPlain(cipher: Cipher, userEnc: Uint8Array, userMac: Uint
|
||||
out.login = null;
|
||||
}
|
||||
|
||||
out.card = cipher.card ? await deepDecryptUnknown(cipher.card, keyParts.enc, keyParts.mac) : null;
|
||||
out.identity = cipher.identity ? await deepDecryptUnknown(cipher.identity, keyParts.enc, keyParts.mac) : null;
|
||||
out.card = cipher.card ? await deepDecryptUnknown(cloneWithoutDecodedFields(cipher.card), keyParts.enc, keyParts.mac) : null;
|
||||
out.identity = cipher.identity ? await deepDecryptUnknown(cloneWithoutDecodedFields(cipher.identity), keyParts.enc, keyParts.mac) : null;
|
||||
if (cipher.sshKey) {
|
||||
const fingerprint = await decryptMaybe(
|
||||
cipher.sshKey.keyFingerprint ?? cipher.sshKey.fingerprint ?? null,
|
||||
@@ -340,6 +344,7 @@ async function mapCipherPlain(cipher: Cipher, userEnc: Uint8Array, userMac: Uint
|
||||
keyParts.mac
|
||||
);
|
||||
out.sshKey = {
|
||||
...((await deepDecryptUnknown(cloneWithoutDecodedFields(cipher.sshKey), keyParts.enc, keyParts.mac)) as Record<string, unknown>),
|
||||
privateKey: await decryptMaybe(cipher.sshKey.privateKey ?? null, keyParts.enc, keyParts.mac),
|
||||
publicKey: await decryptMaybe(cipher.sshKey.publicKey ?? null, keyParts.enc, keyParts.mac),
|
||||
keyFingerprint: fingerprint,
|
||||
@@ -349,6 +354,15 @@ async function mapCipherPlain(cipher: Cipher, userEnc: Uint8Array, userMac: Uint
|
||||
} else {
|
||||
out.sshKey = null;
|
||||
}
|
||||
out.bankAccount = cipher.bankAccount
|
||||
? await deepDecryptUnknown(cloneWithoutDecodedFields(cipher.bankAccount), keyParts.enc, keyParts.mac)
|
||||
: null;
|
||||
out.driversLicense = cipher.driversLicense
|
||||
? await deepDecryptUnknown(cloneWithoutDecodedFields(cipher.driversLicense), keyParts.enc, keyParts.mac)
|
||||
: null;
|
||||
out.passport = cipher.passport
|
||||
? await deepDecryptUnknown(cloneWithoutDecodedFields(cipher.passport), keyParts.enc, keyParts.mac)
|
||||
: null;
|
||||
out.secureNote = cipher.secureNote
|
||||
? {
|
||||
type: normalizeNumber((cipher.secureNote as { type?: unknown }).type, 0),
|
||||
@@ -431,6 +445,9 @@ function sourceTypeLabel(type: number): string {
|
||||
if (type === 3) return 'card';
|
||||
if (type === 4) return 'identity';
|
||||
if (type === 5) return 'sshKey';
|
||||
if (type === 6) return 'bankAccount';
|
||||
if (type === 7) return 'driversLicense';
|
||||
if (type === 8) return 'passport';
|
||||
if (type === 2) return 'note';
|
||||
return `type ${type}`;
|
||||
}
|
||||
@@ -449,6 +466,16 @@ function appendRecordFieldLines(lines: string[], prefix: string, value: unknown)
|
||||
}
|
||||
}
|
||||
|
||||
function cloneWithoutDecodedFields(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const [key, item] of Object.entries(value)) {
|
||||
if (/^dec[A-Z]/.test(key)) continue;
|
||||
out[key] = cloneValue(item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const BITWARDEN_CSV_OBJECT_FIELDS: Record<string, readonly string[]> = {
|
||||
card: ['cardholderName', 'brand', 'number', 'expMonth', 'expYear', 'code'],
|
||||
identity: [
|
||||
@@ -472,6 +499,9 @@ const BITWARDEN_CSV_OBJECT_FIELDS: Record<string, readonly string[]> = {
|
||||
'country',
|
||||
],
|
||||
sshKey: ['privateKey', 'publicKey', 'keyFingerprint', 'fingerprint'],
|
||||
bankAccount: ['bankName', 'nameOnAccount', 'accountType', 'accountNumber', 'routingNumber', 'branchNumber', 'pin', 'swiftCode', 'iban', 'bankContactPhone'],
|
||||
driversLicense: ['firstName', 'middleName', 'lastName', 'dateOfBirth', 'licenseNumber', 'issuingCountry', 'issuingState', 'issueDate', 'expirationDate', 'issuingAuthority', 'licenseClass'],
|
||||
passport: ['surname', 'givenName', 'dateOfBirth', 'sex', 'birthPlace', 'nationality', 'issuingCountry', 'passportNumber', 'passportType', 'nationalIdentificationNumber', 'issuingAuthority', 'issueDate', 'expirationDate'],
|
||||
};
|
||||
|
||||
function appendKnownRecordFieldLines(lines: string[], prefix: string, value: unknown): void {
|
||||
|
||||
@@ -688,6 +688,35 @@ const en: Record<string, string> = {
|
||||
"txt_last_name": "Last Name",
|
||||
"txt_last_seen": "Last Seen",
|
||||
"txt_license_number": "License Number",
|
||||
"txt_bank_account": "Bank Account",
|
||||
"txt_bank_account_details": "Bank Account Details",
|
||||
"txt_bank_name": "Bank Name",
|
||||
"txt_name_on_account": "Name on Account",
|
||||
"txt_account_type": "Account Type",
|
||||
"txt_account_number": "Account Number",
|
||||
"txt_routing_number": "Routing Number",
|
||||
"txt_branch_number": "Branch Number",
|
||||
"txt_pin": "PIN",
|
||||
"txt_swift_code": "SWIFT Code",
|
||||
"txt_iban": "IBAN",
|
||||
"txt_bank_contact_phone": "Bank Contact Phone",
|
||||
"txt_drivers_license": "Driver License",
|
||||
"txt_drivers_license_details": "Driver License Details",
|
||||
"txt_date_of_birth": "Date of Birth",
|
||||
"txt_issuing_country": "Issuing Country",
|
||||
"txt_issuing_state": "Issuing State",
|
||||
"txt_issue_date": "Issue Date",
|
||||
"txt_issuing_authority": "Issuing Authority",
|
||||
"txt_license_class": "License Class",
|
||||
"txt_passport": "Passport",
|
||||
"txt_passport_details": "Passport Details",
|
||||
"txt_surname": "Surname",
|
||||
"txt_given_name": "Given Name",
|
||||
"txt_sex": "Sex",
|
||||
"txt_birth_place": "Place of Birth",
|
||||
"txt_nationality": "Nationality",
|
||||
"txt_passport_type": "Passport Type",
|
||||
"txt_national_id_number": "National ID Number",
|
||||
"txt_link_copied": "Link copied",
|
||||
"txt_linked": "Linked",
|
||||
"txt_linux_desktop": "Linux Desktop",
|
||||
|
||||
@@ -688,6 +688,35 @@ const es: Record<string, string> = {
|
||||
"txt_last_name": "Apellido",
|
||||
"txt_last_seen": "Visto por última vez",
|
||||
"txt_license_number": "Número de licencia",
|
||||
"txt_bank_account": "Cuenta bancaria",
|
||||
"txt_bank_account_details": "Detalles de cuenta bancaria",
|
||||
"txt_bank_name": "Nombre del banco",
|
||||
"txt_name_on_account": "Nombre en la cuenta",
|
||||
"txt_account_type": "Tipo de cuenta",
|
||||
"txt_account_number": "Número de cuenta",
|
||||
"txt_routing_number": "Número de ruta",
|
||||
"txt_branch_number": "Número de sucursal",
|
||||
"txt_pin": "PIN",
|
||||
"txt_swift_code": "Código SWIFT",
|
||||
"txt_iban": "IBAN",
|
||||
"txt_bank_contact_phone": "Teléfono del banco",
|
||||
"txt_drivers_license": "Licencia de conducir",
|
||||
"txt_drivers_license_details": "Detalles de licencia de conducir",
|
||||
"txt_date_of_birth": "Fecha de nacimiento",
|
||||
"txt_issuing_country": "País emisor",
|
||||
"txt_issuing_state": "Estado emisor",
|
||||
"txt_issue_date": "Fecha de emisión",
|
||||
"txt_issuing_authority": "Autoridad emisora",
|
||||
"txt_license_class": "Clase de licencia",
|
||||
"txt_passport": "Pasaporte",
|
||||
"txt_passport_details": "Detalles del pasaporte",
|
||||
"txt_surname": "Apellido",
|
||||
"txt_given_name": "Nombre",
|
||||
"txt_sex": "Sexo",
|
||||
"txt_birth_place": "Lugar de nacimiento",
|
||||
"txt_nationality": "Nacionalidad",
|
||||
"txt_passport_type": "Tipo de pasaporte",
|
||||
"txt_national_id_number": "Número de ID nacional",
|
||||
"txt_link_copied": "Enlace copiado",
|
||||
"txt_linked": "Vinculado",
|
||||
"txt_linux_desktop": "Escritorio Linux",
|
||||
|
||||
@@ -688,6 +688,35 @@ const ru: Record<string, string> = {
|
||||
"txt_last_name": "Фамилия",
|
||||
"txt_last_seen": "Последний визит",
|
||||
"txt_license_number": "Номер лицензии",
|
||||
"txt_bank_account": "Банковский счет",
|
||||
"txt_bank_account_details": "Данные банковского счета",
|
||||
"txt_bank_name": "Название банка",
|
||||
"txt_name_on_account": "Имя владельца счета",
|
||||
"txt_account_type": "Тип счета",
|
||||
"txt_account_number": "Номер счета",
|
||||
"txt_routing_number": "Маршрутный номер",
|
||||
"txt_branch_number": "Номер отделения",
|
||||
"txt_pin": "PIN",
|
||||
"txt_swift_code": "SWIFT-код",
|
||||
"txt_iban": "IBAN",
|
||||
"txt_bank_contact_phone": "Телефон банка",
|
||||
"txt_drivers_license": "Водительское удостоверение",
|
||||
"txt_drivers_license_details": "Данные водительского удостоверения",
|
||||
"txt_date_of_birth": "Дата рождения",
|
||||
"txt_issuing_country": "Страна выдачи",
|
||||
"txt_issuing_state": "Регион выдачи",
|
||||
"txt_issue_date": "Дата выдачи",
|
||||
"txt_issuing_authority": "Орган выдачи",
|
||||
"txt_license_class": "Категория",
|
||||
"txt_passport": "Паспорт",
|
||||
"txt_passport_details": "Данные паспорта",
|
||||
"txt_surname": "Фамилия",
|
||||
"txt_given_name": "Имя",
|
||||
"txt_sex": "Пол",
|
||||
"txt_birth_place": "Место рождения",
|
||||
"txt_nationality": "Гражданство",
|
||||
"txt_passport_type": "Тип паспорта",
|
||||
"txt_national_id_number": "Национальный ID",
|
||||
"txt_link_copied": "Ссылка скопирована",
|
||||
"txt_linked": "Связано",
|
||||
"txt_linux_desktop": "Рабочий стол Linux",
|
||||
|
||||
@@ -688,6 +688,35 @@ const zhCN: Record<string, string> = {
|
||||
"txt_last_name": "姓",
|
||||
"txt_last_seen": "最后在线",
|
||||
"txt_license_number": "证件号",
|
||||
"txt_bank_account": "银行账户",
|
||||
"txt_bank_account_details": "银行账户详情",
|
||||
"txt_bank_name": "银行名称",
|
||||
"txt_name_on_account": "账户姓名",
|
||||
"txt_account_type": "账户类型",
|
||||
"txt_account_number": "账户号码",
|
||||
"txt_routing_number": "路由号码",
|
||||
"txt_branch_number": "分行号码",
|
||||
"txt_pin": "PIN",
|
||||
"txt_swift_code": "SWIFT 代码",
|
||||
"txt_iban": "IBAN",
|
||||
"txt_bank_contact_phone": "银行联系电话",
|
||||
"txt_drivers_license": "驾照",
|
||||
"txt_drivers_license_details": "驾照详情",
|
||||
"txt_date_of_birth": "出生日期",
|
||||
"txt_issuing_country": "签发国家/地区",
|
||||
"txt_issuing_state": "签发州/省",
|
||||
"txt_issue_date": "签发日期",
|
||||
"txt_issuing_authority": "签发机构",
|
||||
"txt_license_class": "驾照等级",
|
||||
"txt_passport": "护照",
|
||||
"txt_passport_details": "护照详情",
|
||||
"txt_surname": "姓",
|
||||
"txt_given_name": "名",
|
||||
"txt_sex": "性别",
|
||||
"txt_birth_place": "出生地",
|
||||
"txt_nationality": "国籍",
|
||||
"txt_passport_type": "护照类型",
|
||||
"txt_national_id_number": "国家身份证号",
|
||||
"txt_link_copied": "链接已复制",
|
||||
"txt_linked": "已关联",
|
||||
"txt_linux_desktop": "Linux 桌面端",
|
||||
|
||||
@@ -688,6 +688,35 @@ const zhTW: Record<string, string> = {
|
||||
"txt_last_name": "姓",
|
||||
"txt_last_seen": "最後在線",
|
||||
"txt_license_number": "證件號",
|
||||
"txt_bank_account": "銀行帳戶",
|
||||
"txt_bank_account_details": "銀行帳戶詳情",
|
||||
"txt_bank_name": "銀行名稱",
|
||||
"txt_name_on_account": "帳戶姓名",
|
||||
"txt_account_type": "帳戶類型",
|
||||
"txt_account_number": "帳戶號碼",
|
||||
"txt_routing_number": "路由號碼",
|
||||
"txt_branch_number": "分行號碼",
|
||||
"txt_pin": "PIN",
|
||||
"txt_swift_code": "SWIFT 代碼",
|
||||
"txt_iban": "IBAN",
|
||||
"txt_bank_contact_phone": "銀行聯絡電話",
|
||||
"txt_drivers_license": "駕照",
|
||||
"txt_drivers_license_details": "駕照詳情",
|
||||
"txt_date_of_birth": "出生日期",
|
||||
"txt_issuing_country": "簽發國家/地區",
|
||||
"txt_issuing_state": "簽發州/省",
|
||||
"txt_issue_date": "簽發日期",
|
||||
"txt_issuing_authority": "簽發機構",
|
||||
"txt_license_class": "駕照等級",
|
||||
"txt_passport": "護照",
|
||||
"txt_passport_details": "護照詳情",
|
||||
"txt_surname": "姓",
|
||||
"txt_given_name": "名",
|
||||
"txt_sex": "性別",
|
||||
"txt_birth_place": "出生地",
|
||||
"txt_nationality": "國籍",
|
||||
"txt_passport_type": "護照類型",
|
||||
"txt_national_id_number": "國家身分證號",
|
||||
"txt_link_copied": "鏈接已複製",
|
||||
"txt_linked": "已關聯",
|
||||
"txt_linux_desktop": "Linux 桌面端",
|
||||
|
||||
@@ -40,6 +40,10 @@ export interface BitwardenCipherInput {
|
||||
fields?: BitwardenFieldInput[] | null;
|
||||
passwordHistory?: Array<{ password?: string | null; lastUsedDate?: string | null }> | null;
|
||||
sshKey?: Record<string, unknown> | null;
|
||||
bankAccount?: Record<string, unknown> | null;
|
||||
driversLicense?: Record<string, unknown> | null;
|
||||
passport?: Record<string, unknown> | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface BitwardenJsonInput {
|
||||
@@ -79,6 +83,7 @@ export function normalizeBitwardenImport(raw: unknown): CiphersImportPayload {
|
||||
let hasAnyExplicitFolderLink = false;
|
||||
for (const item of itemsRaw) {
|
||||
ciphers.push({
|
||||
...(item && typeof item === 'object' ? item as Record<string, unknown> : {}),
|
||||
id: item?.id ?? null,
|
||||
type: Number(item?.type || 1) || 1,
|
||||
name: item?.name ?? 'Untitled',
|
||||
@@ -93,7 +98,7 @@ export function normalizeBitwardenImport(raw: unknown): CiphersImportPayload {
|
||||
totp: item.login.totp ?? null,
|
||||
fido2Credentials: Array.isArray(item.login.fido2Credentials) ? item.login.fido2Credentials : null,
|
||||
uris: Array.isArray(item.login.uris)
|
||||
? item.login.uris.map((u) => ({ uri: u?.uri ?? null, match: u?.match ?? null }))
|
||||
? item.login.uris.map((u) => ({ ...u, uri: u?.uri ?? null, uriChecksum: u?.uriChecksum ?? null, match: u?.match ?? null }))
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
@@ -114,6 +119,9 @@ export function normalizeBitwardenImport(raw: unknown): CiphersImportPayload {
|
||||
.filter((x) => !!x.password)
|
||||
: null,
|
||||
sshKey: item?.sshKey ?? null,
|
||||
bankAccount: item?.bankAccount ?? null,
|
||||
driversLicense: item?.driversLicense ?? null,
|
||||
passport: item?.passport ?? null,
|
||||
});
|
||||
const folderId = txt(item?.folderId);
|
||||
if (!folderId) continue;
|
||||
|
||||
@@ -142,6 +142,86 @@ export interface CipherSshKey {
|
||||
decFingerprint?: string;
|
||||
}
|
||||
|
||||
export interface CipherBankAccount {
|
||||
bankName?: string | null;
|
||||
nameOnAccount?: string | null;
|
||||
accountType?: string | null;
|
||||
accountNumber?: string | null;
|
||||
routingNumber?: string | null;
|
||||
branchNumber?: string | null;
|
||||
pin?: string | null;
|
||||
swiftCode?: string | null;
|
||||
iban?: string | null;
|
||||
bankContactPhone?: string | null;
|
||||
decBankName?: string;
|
||||
decNameOnAccount?: string;
|
||||
decAccountType?: string;
|
||||
decAccountNumber?: string;
|
||||
decRoutingNumber?: string;
|
||||
decBranchNumber?: string;
|
||||
decPin?: string;
|
||||
decSwiftCode?: string;
|
||||
decIban?: string;
|
||||
decBankContactPhone?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CipherDriversLicense {
|
||||
firstName?: string | null;
|
||||
middleName?: string | null;
|
||||
lastName?: string | null;
|
||||
dateOfBirth?: string | null;
|
||||
licenseNumber?: string | null;
|
||||
issuingCountry?: string | null;
|
||||
issuingState?: string | null;
|
||||
issueDate?: string | null;
|
||||
expirationDate?: string | null;
|
||||
issuingAuthority?: string | null;
|
||||
licenseClass?: string | null;
|
||||
decFirstName?: string;
|
||||
decMiddleName?: string;
|
||||
decLastName?: string;
|
||||
decDateOfBirth?: string;
|
||||
decLicenseNumber?: string;
|
||||
decIssuingCountry?: string;
|
||||
decIssuingState?: string;
|
||||
decIssueDate?: string;
|
||||
decExpirationDate?: string;
|
||||
decIssuingAuthority?: string;
|
||||
decLicenseClass?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CipherPassport {
|
||||
surname?: string | null;
|
||||
givenName?: string | null;
|
||||
dateOfBirth?: string | null;
|
||||
sex?: string | null;
|
||||
birthPlace?: string | null;
|
||||
nationality?: string | null;
|
||||
issuingCountry?: string | null;
|
||||
passportNumber?: string | null;
|
||||
passportType?: string | null;
|
||||
nationalIdentificationNumber?: string | null;
|
||||
issuingAuthority?: string | null;
|
||||
issueDate?: string | null;
|
||||
expirationDate?: string | null;
|
||||
decSurname?: string;
|
||||
decGivenName?: string;
|
||||
decDateOfBirth?: string;
|
||||
decSex?: string;
|
||||
decBirthPlace?: string;
|
||||
decNationality?: string;
|
||||
decIssuingCountry?: string;
|
||||
decPassportNumber?: string;
|
||||
decPassportType?: string;
|
||||
decNationalIdentificationNumber?: string;
|
||||
decIssuingAuthority?: string;
|
||||
decIssueDate?: string;
|
||||
decExpirationDate?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CipherField {
|
||||
type?: number | string | null;
|
||||
name?: string | null;
|
||||
@@ -175,6 +255,9 @@ export interface Cipher {
|
||||
card?: CipherCard | null;
|
||||
identity?: CipherIdentity | null;
|
||||
sshKey?: CipherSshKey | null;
|
||||
bankAccount?: CipherBankAccount | null;
|
||||
driversLicense?: CipherDriversLicense | null;
|
||||
passport?: CipherPassport | null;
|
||||
secureNote?: { type?: number | null } | null;
|
||||
passwordHistory?: CipherPasswordHistoryEntry[] | null;
|
||||
fields?: CipherField[] | null;
|
||||
@@ -276,6 +359,40 @@ export interface VaultDraft {
|
||||
sshPrivateKey: string;
|
||||
sshPublicKey: string;
|
||||
sshFingerprint: string;
|
||||
bankName: string;
|
||||
bankNameOnAccount: string;
|
||||
bankAccountType: string;
|
||||
bankAccountNumber: string;
|
||||
bankRoutingNumber: string;
|
||||
bankBranchNumber: string;
|
||||
bankPin: string;
|
||||
bankSwiftCode: string;
|
||||
bankIban: string;
|
||||
bankContactPhone: string;
|
||||
licenseFirstName: string;
|
||||
licenseMiddleName: string;
|
||||
licenseLastName: string;
|
||||
licenseDateOfBirth: string;
|
||||
licenseNumber: string;
|
||||
licenseIssuingCountry: string;
|
||||
licenseIssuingState: string;
|
||||
licenseIssueDate: string;
|
||||
licenseExpirationDate: string;
|
||||
licenseIssuingAuthority: string;
|
||||
licenseClass: string;
|
||||
passportSurname: string;
|
||||
passportGivenName: string;
|
||||
passportDateOfBirth: string;
|
||||
passportSex: string;
|
||||
passportBirthPlace: string;
|
||||
passportNationality: string;
|
||||
passportIssuingCountry: string;
|
||||
passportNumber: string;
|
||||
passportType: string;
|
||||
passportNationalIdentificationNumber: string;
|
||||
passportIssuingAuthority: string;
|
||||
passportIssueDate: string;
|
||||
passportExpirationDate: string;
|
||||
customFields: VaultDraftField[];
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,31 @@ async function decryptCipherField(
|
||||
return looksLikeCipherString(value) ? '' : value;
|
||||
}
|
||||
|
||||
async function decryptCipherObjectFields<T extends Record<string, unknown>>(
|
||||
source: T | null | undefined,
|
||||
fields: readonly string[],
|
||||
itemEnc: Uint8Array,
|
||||
itemMac: Uint8Array,
|
||||
userEnc: Uint8Array,
|
||||
userMac: Uint8Array,
|
||||
canFallbackToUserKey: boolean
|
||||
): Promise<T | null | undefined> {
|
||||
if (!source || typeof source !== 'object') return source;
|
||||
const next: Record<string, unknown> = { ...source };
|
||||
for (const field of fields) {
|
||||
const decKey = `dec${field.charAt(0).toUpperCase()}${field.slice(1)}`;
|
||||
next[decKey] = await decryptCipherField(
|
||||
source[field] as string | null | undefined,
|
||||
itemEnc,
|
||||
itemMac,
|
||||
userEnc,
|
||||
userMac,
|
||||
canFallbackToUserKey
|
||||
);
|
||||
}
|
||||
return next as T;
|
||||
}
|
||||
|
||||
async function decryptFieldWithSource(
|
||||
value: string | null | undefined,
|
||||
itemEnc: Uint8Array,
|
||||
@@ -200,6 +225,42 @@ export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<Decr
|
||||
};
|
||||
}
|
||||
|
||||
if (cipher.bankAccount) {
|
||||
nextCipher.bankAccount = await decryptCipherObjectFields(
|
||||
cipher.bankAccount,
|
||||
['bankName', 'nameOnAccount', 'accountType', 'accountNumber', 'routingNumber', 'branchNumber', 'pin', 'swiftCode', 'iban', 'bankContactPhone'],
|
||||
itemEnc,
|
||||
itemMac,
|
||||
userEnc,
|
||||
userMac,
|
||||
canFallbackToUserKey
|
||||
);
|
||||
}
|
||||
|
||||
if (cipher.driversLicense) {
|
||||
nextCipher.driversLicense = await decryptCipherObjectFields(
|
||||
cipher.driversLicense,
|
||||
['firstName', 'middleName', 'lastName', 'dateOfBirth', 'licenseNumber', 'issuingCountry', 'issuingState', 'issueDate', 'expirationDate', 'issuingAuthority', 'licenseClass'],
|
||||
itemEnc,
|
||||
itemMac,
|
||||
userEnc,
|
||||
userMac,
|
||||
canFallbackToUserKey
|
||||
);
|
||||
}
|
||||
|
||||
if (cipher.passport) {
|
||||
nextCipher.passport = await decryptCipherObjectFields(
|
||||
cipher.passport,
|
||||
['surname', 'givenName', 'dateOfBirth', 'sex', 'birthPlace', 'nationality', 'issuingCountry', 'passportNumber', 'passportType', 'nationalIdentificationNumber', 'issuingAuthority', 'issueDate', 'expirationDate'],
|
||||
itemEnc,
|
||||
itemMac,
|
||||
userEnc,
|
||||
userMac,
|
||||
canFallbackToUserKey
|
||||
);
|
||||
}
|
||||
|
||||
if (cipher.fields) {
|
||||
nextCipher.fields = await Promise.all(
|
||||
cipher.fields.map(async (field) => ({
|
||||
|
||||
Reference in New Issue
Block a user