Fix: hide passwordless toggle when disabled, remove lock auto-trigger

1. Passwordless toggle in Settings is now hidden when admin hasn't
   enabled allow_passwordless in system config (or when user already
   has it enabled — so they can still disable it). Backend exposes
   allow_passwordless in /auth/status response.

2. Remove auto-trigger passkey ceremony on lock screen — previously
   fired immediately when session locked for passwordless users.
   Now waits for user to click "Unlock with passkey" button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-18 00:32:03 +08:00
parent 42d73526f5
commit 1b868ba503
5 changed files with 36 additions and 39 deletions

View File

@ -553,6 +553,7 @@ async def auth_status(
"has_passkeys": has_passkeys,
"passkey_count": passkey_count,
"passwordless_enabled": passwordless_enabled,
"allow_passwordless": config.allow_passwordless if config else False,
}

View File

@ -39,14 +39,6 @@ export default function LockOverlay() {
}
}, [isLocked, showPasswordForm]);
// Auto-trigger passkey unlock when passwordless mode is enabled
useEffect(() => {
if (isLocked && passwordlessEnabled && supportsWebAuthn && !isPasskeyUnlocking) {
handlePasskeyUnlock();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLocked, passwordlessEnabled]);
if (!isLocked) return null;
const preferredName = settings?.preferred_name;

View File

@ -137,7 +137,7 @@ function PasskeyDeleteButton({ credential, onDelete, isDeleting }: DeleteConfirm
export default function PasskeySection() {
const queryClient = useQueryClient();
const { passwordlessEnabled, passkeyCount } = useAuth();
const { passwordlessEnabled, passkeyCount, allowPasswordless } = useAuth();
// Registration state
const [registerDialogOpen, setRegisterDialogOpen] = useState(false);
@ -376,9 +376,10 @@ export default function PasskeySection() {
{hasPasskeys ? 'Add another passkey' : 'Add a passkey'}
</Button>
{/* Passwordless login section */}
<Separator />
{/* Passwordless login section — hidden when admin hasn't enabled the feature */}
{(allowPasswordless || passwordlessEnabled) && <Separator />}
{(allowPasswordless || passwordlessEnabled) && (
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<div className="flex items-center gap-2">
@ -388,7 +389,7 @@ export default function PasskeySection() {
<p className="text-xs text-muted-foreground">
Skip the password prompt and unlock the app using a passkey only.
</p>
{passkeyCount < 2 && (
{passkeyCount < 2 && !passwordlessEnabled && (
<p className="text-xs text-amber-400">
Requires at least 2 registered passkeys as a fallback.
</p>
@ -409,6 +410,7 @@ export default function PasskeySection() {
aria-label="Toggle passwordless login"
/>
</div>
)}
{/* Enable passwordless dialog */}
<Dialog open={enableDialogOpen} onOpenChange={(open) => {

View File

@ -154,5 +154,6 @@ export function useAuth() {
hasPasskeys: authQuery.data?.has_passkeys ?? false,
passkeyCount: authQuery.data?.passkey_count ?? 0,
passwordlessEnabled: authQuery.data?.passwordless_enabled ?? false,
allowPasswordless: authQuery.data?.allow_passwordless ?? false,
};
}

View File

@ -246,6 +246,7 @@ export interface AuthStatus {
has_passkeys: boolean;
passkey_count: number;
passwordless_enabled: boolean;
allow_passwordless: boolean;
}
export interface PasskeyCredential {