feat: add FIDO2 credentials support in cipher handling and UI components

This commit is contained in:
shuaiplus
2026-04-08 14:40:49 +08:00
parent c516194d54
commit 5bf7c79ada
12 changed files with 113 additions and 5 deletions
+2
View File
@@ -16,6 +16,7 @@ import {
draftFromCipher,
buildCipherDuplicateSignature,
firstCipherUri,
firstPasskeyCreationTime,
isCipherVisibleInArchive,
isCipherVisibleInNormalVault,
isCipherVisibleInTrash,
@@ -971,6 +972,7 @@ function folderName(id: string | null | undefined): string {
repromptApprovedCipherId={repromptApprovedCipherId}
showPassword={showPassword}
totpLive={totpLive}
passkeyCreatedAt={firstPasskeyCreationTime(selectedCipher)}
hiddenFieldVisibleMap={hiddenFieldVisibleMap}
folderName={folderName}
onOpenReprompt={() => setRepromptOpen(true)}
@@ -20,6 +20,7 @@ interface VaultDetailViewProps {
repromptApprovedCipherId: string | null;
showPassword: boolean;
totpLive: { code: string; remain: number } | null;
passkeyCreatedAt: string | null;
hiddenFieldVisibleMap: Record<number, boolean>;
folderName: (id: string | null | undefined) => string;
downloadingAttachmentKey: string;
@@ -135,6 +136,15 @@ export default function VaultDetailView(props: VaultDetailViewProps) {
</div>
</div>
)}
{!!props.passkeyCreatedAt && (
<div className="kv-row">
<span className="kv-label">{t('txt_passkey')}</span>
<div className="kv-main">
<strong>{t('txt_passkey_created_at_value', { value: formatHistoryTime(props.passkeyCreatedAt) })}</strong>
</div>
<div className="kv-actions" />
</div>
)}
</div>
)}
@@ -194,6 +194,9 @@ export function buildCipherDuplicateSignature(cipher: Cipher): string {
uri: valueOrFallback(uri.decUri ?? uri.uri),
match: uri.match ?? null,
})),
fido2Credentials: (cipher.login.fido2Credentials || []).map((credential) => ({
creationDate: valueOrFallback(credential.creationDate),
})),
}
: null,
card: cipher.card
@@ -262,6 +265,7 @@ export function createEmptyDraft(type: number): VaultDraft {
loginPassword: '',
loginTotp: '',
loginUris: [createEmptyLoginUri()],
loginFido2Credentials: [],
cardholderName: '',
cardNumber: '',
cardBrand: '',
@@ -310,6 +314,9 @@ export function draftFromCipher(cipher: Cipher): VaultDraft {
uri: x.decUri || x.uri || '',
match: x.match ?? null,
}));
draft.loginFido2Credentials = Array.isArray(cipher.login.fido2Credentials)
? cipher.login.fido2Credentials.map((credential) => ({ ...credential }))
: [];
if (!draft.loginUris.length) draft.loginUris = [createEmptyLoginUri()];
}
if (cipher.card) {
@@ -406,6 +413,16 @@ export function creationTimeValue(cipher: Cipher): number {
return Number.isFinite(time) ? time : 0;
}
export function firstPasskeyCreationTime(cipher: Cipher | null): string | null {
const credentials = cipher?.login?.fido2Credentials;
if (!Array.isArray(credentials) || credentials.length === 0) return null;
for (const credential of credentials) {
const raw = String(credential?.creationDate || '').trim();
if (raw) return raw;
}
return null;
}
const failedIconHosts = new Set<string>();
export function VaultListIcon({ cipher }: { cipher: Cipher }) {