Cleanup passkey create and authenticate flows (#520)

This commit is contained in:
Leendert de Borst
2025-10-05 10:05:53 +02:00
parent 405b44383f
commit 3b1199d2db
3 changed files with 36 additions and 85 deletions

View File

@@ -8,7 +8,6 @@ import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
import { useVaultLockRedirect } from '@/entrypoints/popup/hooks/useVaultLockRedirect';
import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
import { PasskeyAuthenticator } from '@/utils/passkey/PasskeyAuthenticator';
import { PasskeyHelper } from '@/utils/passkey/PasskeyHelper';
@@ -22,9 +21,7 @@ const PasskeyAuthenticate: React.FC = () => {
const location = useLocation();
const { setIsInitialLoading } = useLoading();
const dbContext = useDb();
const { executeVaultMutation } = useVaultMutate();
const [request, setRequest] = useState<PendingPasskeyGetRequest | null>(null);
const [selectedPasskey, setSelectedPasskey] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [availablePasskeys, setAvailablePasskeys] = useState<Array<{ id: string; displayName: string; username?: string | null }>>([]);
@@ -165,54 +162,6 @@ const PasskeyAuthenticate: React.FC = () => {
}
};
/**
* Handle passkey deletion
*/
const handleDeletePasskey = async (passkeyId: string, event: React.MouseEvent) : Promise<void> => {
event.stopPropagation(); // Prevent triggering authentication
if (!confirm(t('passkeys.authenticate.deleteConfirm'))) {
return;
}
if (!dbContext.sqliteClient) {
return;
}
try {
// Delete via vault mutation to sync changes
await executeVaultMutation(
async () => {
await dbContext.sqliteClient!.deletePasskeyById(passkeyId);
},
{
/**
* onSuccess
*/
onSuccess: () => {
// Remove from UI
setAvailablePasskeys(prev => prev.filter(pk => pk.id !== passkeyId));
// Clear selection if the deleted passkey was selected
if (selectedPasskey === passkeyId) {
setSelectedPasskey(null);
}
},
/**
* onError
*/
onError: (err) => {
console.error('Failed to delete passkey:', err);
setError(t('common.errors.unknownError'));
}
}
);
} catch (error) {
console.error('Failed to delete passkey:', error);
setError(t('common.errors.unknownError'));
}
};
/**
* Handle fallback
*/
@@ -282,31 +231,17 @@ const PasskeyAuthenticate: React.FC = () => {
{availablePasskeys.map((pk) => (
<div
key={pk.id}
className="relative group p-3 rounded-lg border cursor-pointer transition-colors bg-white border-gray-200 hover:bg-blue-50 hover:border-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:hover:bg-blue-900 dark:hover:border-blue-700"
className="p-3 rounded-lg border cursor-pointer transition-colors bg-white border-gray-200 hover:bg-blue-50 hover:border-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:hover:bg-blue-900 dark:hover:border-blue-700"
onClick={() => !loading && handleUsePasskey(pk.id)}
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="font-medium text-gray-900 dark:text-white text-sm truncate">
{pk.displayName}
</div>
{pk.username && (
<div className="text-xs text-gray-600 dark:text-gray-400">
{pk.username}
</div>
)}
</div>
<button
onClick={(e) => handleDeletePasskey(pk.id, e)}
className="flex-shrink-0 p-1.5 text-gray-400 hover:text-red-600 dark:text-gray-500 dark:hover:text-red-400 transition-colors opacity-0 group-hover:opacity-100"
title={t('passkeys.authenticate.deletePasskey')}
disabled={loading}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
<div className="font-medium text-gray-900 dark:text-white text-sm truncate">
{pk.displayName}
</div>
{pk.username && (
<div className="text-xs text-gray-600 dark:text-gray-400">
{pk.username}
</div>
)}
</div>
))}
</div>

View File

@@ -491,15 +491,33 @@ const PasskeyCreate: React.FC = () => {
{selectedPasskeyToReplace ? t('passkeys.create.confirmReplace') : t('passkeys.create.createButton')}
</Button>
<Button
variant="secondary"
onClick={() => {
setShowCreateForm(false);
setSelectedPasskeyToReplace(null);
}}
>
{t('common.back')}
</Button>
{existingPasskeys.length > 0 ? (
<Button
variant="secondary"
onClick={() => {
setShowCreateForm(false);
setSelectedPasskeyToReplace(null);
}}
>
{t('common.back')}
</Button>
) : (
<>
<Button
variant="secondary"
onClick={handleFallback}
>
{t('passkeys.create.useBrowserPasskey')}
</Button>
<Button
variant="secondary"
onClick={handleCancel}
>
{t('common.cancel')}
</Button>
</>
)}
</div>
</div>
)}

View File

@@ -365,9 +365,7 @@
"signInFor": "Sign in with passkey for",
"selectPasskey": "Select a passkey to sign in:",
"noPasskeysFound": "No passkeys found for this site",
"useBrowserPasskey": "Use Browser Passkey",
"deletePasskey": "Delete passkey",
"deleteConfirm": "Are you sure you want to delete this passkey?"
"useBrowserPasskey": "Use Browser Passkey"
},
"create": {
"title": "Create Passkey",