'use client' import * as React from 'react' import { browserSupportsWebAuthn, startRegistration } from '@simplewebauthn/browser' import { formatDistanceToNow } from 'date-fns' import { Eye, EyeOff, Loader2, Shield, Trash2 } from 'lucide-react' import { useT } from '@open-mercato/shared/lib/i18n/context' import { flash } from '@open-mercato/ui/backend/FlashMessages' import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall' import { Badge } from '@open-mercato/ui/primitives/badge' import { Button } from '@open-mercato/ui/primitives/button' import { IconButton } from '@open-mercato/ui/primitives/icon-button' import { Input } from '@open-mercato/ui/primitives/input' import type { MfaMethod } from '../types' import MfaConfiguredBadge from './mfa-provider-list-items/MfaConfiguredBadge' type RegisterOptionsResponse = { setupId: string clientData?: Record } type RegisterResponse = { ok?: boolean recoveryCodes?: string[] } function readErrorMessage(error: unknown): string { if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') { return error.message } return 'Passkey registration failed.' } function isAbortError(error: unknown): boolean { const name = error && typeof error === 'object' && 'name' in error ? String(error.name) : '' if (name === 'AbortError' || name === 'NotAllowedError') { return true } const message = readErrorMessage(error).toLowerCase() return message.includes('cancel') || message.includes('aborted') || message.includes('not allowed') } type PasskeyProviderDetailsProps = { methods?: MfaMethod[] saving?: boolean onRemoveMethod?: (method: MfaMethod) => Promise | void onMethodAdded?: () => Promise | void } function formatRelative(value: string | null, fallback: string): string { if (!value) return fallback const parsed = new Date(value) if (Number.isNaN(parsed.getTime())) return fallback return formatDistanceToNow(parsed, { addSuffix: true }) } export default function PasskeyProviderDetails({ methods = [], saving = false, onRemoveMethod, onMethodAdded, }: PasskeyProviderDetailsProps) { const t = useT() const [label, setLabel] = React.useState('') const [loading, setLoading] = React.useState(false) const handleAddPasskey = React.useCallback(async () => { if (!browserSupportsWebAuthn()) { flash( t('security.profile.mfa.passkey.unsupported', 'Passkeys are not supported by this browser.'), 'error', ) return } setLoading(true) try { const optionsResult = await readApiResultOrThrow( '/api/security/mfa/provider/passkey', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ label: label.trim() || undefined, }), }, ) const registrationResponse = await startRegistration({ optionsJSON: (optionsResult.clientData ?? {}) as never, }) await readApiResultOrThrow( '/api/security/mfa/provider/passkey', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ setupId: optionsResult.setupId, payload: { response: registrationResponse, label: label.trim() || undefined, }, }), }, ) setLabel('') await onMethodAdded?.() flash(t('security.profile.mfa.passkey.success', 'Passkey enabled.'), 'success') } catch (error) { if (!isAbortError(error)) { flash(readErrorMessage(error), 'error') } } finally { setLoading(false) } }, [label, onMethodAdded, t]) const configuredCount = methods.length const keysLabel = configuredCount === 1 ? t('security.profile.mfa.providers.passkey.keySingle', '1 key') : t('security.profile.mfa.providers.passkey.keyMany', '{count} keys', { count: String(configuredCount) }) return (

{t( 'security.profile.mfa.passkey.description', 'Security keys are WebAuthn credentials that can only be used as a second factor of authentication.', )}

{configuredCount > 0 ? (
{methods.map((method) => (

{method.label ?? t('security.profile.mfa.providers.passkey.title', 'Security key')}

{t('ui.table.registeredAt', 'Registered')}: {formatRelative(method.createdAt, t('security.profile.mfa.method.unknownTime', 'Unknown'))} {' '}|{' '} {t('security.profile.mfa.method.lastUsed', 'Last used')}: {formatRelative(method.lastUsedAt, t('security.profile.mfa.method.neverUsed', 'Never used'))}

{onRemoveMethod ? ( { void onRemoveMethod(method) }} > ) : null}
))}
) : null}
{ event.preventDefault() if (loading) return void handleAddPasskey() }} > setLabel(event.target.value)} placeholder={t('security.profile.mfa.passkey.nicknamePlaceholder', 'Enter a nickname for this security key')} className="h-10 border-slate-700 bg-slate-950 text-slate-100 placeholder:text-slate-500" />
) }