feat: add FIDO2 credentials support to CipherLogin and VaultDraft types

- Introduced CipherLoginPasskey interface to represent FIDO2 credentials with a creation date.
- Updated CipherLogin interface to include an optional fido2Credentials property.
- Modified VaultDraft interface to add loginFido2Credentials property for handling FIDO2 credentials.
This commit is contained in:
shuaiplus
2026-03-03 02:18:26 +08:00
parent 4da5525a1a
commit b5284e669a
10 changed files with 3359 additions and 25 deletions
+24
View File
@@ -158,6 +158,7 @@ function createEmptyDraft(type: number): VaultDraft {
loginPassword: '',
loginTotp: '',
loginUris: [''],
loginFido2Credentials: [],
cardholderName: '',
cardNumber: '',
cardBrand: '',
@@ -203,6 +204,9 @@ function draftFromCipher(cipher: Cipher): VaultDraft {
draft.loginPassword = cipher.login.decPassword || '';
draft.loginTotp = cipher.login.decTotp || '';
draft.loginUris = (cipher.login.uris || []).map((x) => x.decUri || x.uri || '');
draft.loginFido2Credentials = Array.isArray(cipher.login.fido2Credentials)
? cipher.login.fido2Credentials.map((credential) => ({ ...credential }))
: [];
if (!draft.loginUris.length) draft.loginUris = [''];
}
if (cipher.card) {
@@ -264,6 +268,16 @@ function formatHistoryTime(value: string | null | undefined): string {
return date.toLocaleString();
}
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 TOTP_PERIOD_SECONDS = 30;
const TOTP_RING_RADIUS = 14;
const TOTP_RING_CIRCUMFERENCE = 2 * Math.PI * TOTP_RING_RADIUS;
@@ -419,6 +433,7 @@ export default function VaultPage(props: VaultPageProps) {
() => props.ciphers.find((x) => x.id === selectedCipherId) || null,
[props.ciphers, selectedCipherId]
);
const passkeyCreatedAt = firstPasskeyCreationTime(selectedCipher);
useEffect(() => {
const raw = selectedCipher?.login?.decTotp || '';
@@ -1172,6 +1187,15 @@ function folderName(id: string | null | undefined): string {
</div>
</div>
)}
{!!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(passkeyCreatedAt) })}</strong>
</div>
<div className="kv-actions" />
</div>
)}
</div>
)}