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:
parent
42d73526f5
commit
1b868ba503
@ -553,6 +553,7 @@ async def auth_status(
|
|||||||
"has_passkeys": has_passkeys,
|
"has_passkeys": has_passkeys,
|
||||||
"passkey_count": passkey_count,
|
"passkey_count": passkey_count,
|
||||||
"passwordless_enabled": passwordless_enabled,
|
"passwordless_enabled": passwordless_enabled,
|
||||||
|
"allow_passwordless": config.allow_passwordless if config else False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -39,14 +39,6 @@ export default function LockOverlay() {
|
|||||||
}
|
}
|
||||||
}, [isLocked, showPasswordForm]);
|
}, [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;
|
if (!isLocked) return null;
|
||||||
|
|
||||||
const preferredName = settings?.preferred_name;
|
const preferredName = settings?.preferred_name;
|
||||||
|
|||||||
@ -137,7 +137,7 @@ function PasskeyDeleteButton({ credential, onDelete, isDeleting }: DeleteConfirm
|
|||||||
|
|
||||||
export default function PasskeySection() {
|
export default function PasskeySection() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { passwordlessEnabled, passkeyCount } = useAuth();
|
const { passwordlessEnabled, passkeyCount, allowPasswordless } = useAuth();
|
||||||
|
|
||||||
// Registration state
|
// Registration state
|
||||||
const [registerDialogOpen, setRegisterDialogOpen] = useState(false);
|
const [registerDialogOpen, setRegisterDialogOpen] = useState(false);
|
||||||
@ -376,9 +376,10 @@ export default function PasskeySection() {
|
|||||||
{hasPasskeys ? 'Add another passkey' : 'Add a passkey'}
|
{hasPasskeys ? 'Add another passkey' : 'Add a passkey'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Passwordless login section */}
|
{/* Passwordless login section — hidden when admin hasn't enabled the feature */}
|
||||||
<Separator />
|
{(allowPasswordless || passwordlessEnabled) && <Separator />}
|
||||||
|
|
||||||
|
{(allowPasswordless || passwordlessEnabled) && (
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -388,7 +389,7 @@ export default function PasskeySection() {
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Skip the password prompt and unlock the app using a passkey only.
|
Skip the password prompt and unlock the app using a passkey only.
|
||||||
</p>
|
</p>
|
||||||
{passkeyCount < 2 && (
|
{passkeyCount < 2 && !passwordlessEnabled && (
|
||||||
<p className="text-xs text-amber-400">
|
<p className="text-xs text-amber-400">
|
||||||
Requires at least 2 registered passkeys as a fallback.
|
Requires at least 2 registered passkeys as a fallback.
|
||||||
</p>
|
</p>
|
||||||
@ -409,6 +410,7 @@ export default function PasskeySection() {
|
|||||||
aria-label="Toggle passwordless login"
|
aria-label="Toggle passwordless login"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Enable passwordless dialog */}
|
{/* Enable passwordless dialog */}
|
||||||
<Dialog open={enableDialogOpen} onOpenChange={(open) => {
|
<Dialog open={enableDialogOpen} onOpenChange={(open) => {
|
||||||
|
|||||||
@ -154,5 +154,6 @@ export function useAuth() {
|
|||||||
hasPasskeys: authQuery.data?.has_passkeys ?? false,
|
hasPasskeys: authQuery.data?.has_passkeys ?? false,
|
||||||
passkeyCount: authQuery.data?.passkey_count ?? 0,
|
passkeyCount: authQuery.data?.passkey_count ?? 0,
|
||||||
passwordlessEnabled: authQuery.data?.passwordless_enabled ?? false,
|
passwordlessEnabled: authQuery.data?.passwordless_enabled ?? false,
|
||||||
|
allowPasswordless: authQuery.data?.allow_passwordless ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -246,6 +246,7 @@ export interface AuthStatus {
|
|||||||
has_passkeys: boolean;
|
has_passkeys: boolean;
|
||||||
passkey_count: number;
|
passkey_count: number;
|
||||||
passwordless_enabled: boolean;
|
passwordless_enabled: boolean;
|
||||||
|
allow_passwordless: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PasskeyCredential {
|
export interface PasskeyCredential {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user