mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-30 07:35:15 +02:00
Update security key error handling and user feedback. Improve user guidance for security key interactions and Implement proper error handling for permission denials and timing issues.
This commit is contained in:
parent
3994b25a71
commit
6ccc05b183
4 changed files with 127 additions and 31 deletions
|
@ -1152,5 +1152,11 @@
|
||||||
"securityKeyAuthError": "Failed to authenticate with security key",
|
"securityKeyAuthError": "Failed to authenticate with security key",
|
||||||
"securityKeyRecommendation": "Consider registering another security key on a different device to ensure you don't get locked out of your account.",
|
"securityKeyRecommendation": "Consider registering another security key on a different device to ensure you don't get locked out of your account.",
|
||||||
"registering": "Registering...",
|
"registering": "Registering...",
|
||||||
"securityKeyPrompt": "Please verify your identity using your security key. Make sure your security key is connected and ready."
|
"securityKeyPrompt": "Please verify your identity using your security key. Make sure your security key is connected and ready.",
|
||||||
|
"securityKeyBrowserNotSupported": "Your browser doesn't support security keys. Please use a modern browser like Chrome, Firefox, or Safari.",
|
||||||
|
"securityKeyPermissionDenied": "Please allow access to your security key to continue signing in.",
|
||||||
|
"securityKeyRemovedTooQuickly": "Please keep your security key connected until the sign-in process completes.",
|
||||||
|
"securityKeyNotSupported": "Your security key may not be compatible. Please try a different security key.",
|
||||||
|
"securityKeyUnknownError": "There was a problem using your security key. Please try again.",
|
||||||
|
"twoFactorRequired": "Two-factor authentication is required to register a security key."
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,15 @@ import { verifyPassword } from "@server/auth/password";
|
||||||
import { unauthorized } from "@server/auth/unauthorizedResponse";
|
import { unauthorized } from "@server/auth/unauthorizedResponse";
|
||||||
|
|
||||||
// The RP ID is the domain name of your application
|
// The RP ID is the domain name of your application
|
||||||
const rpID = new URL(config.getRawConfig().app.dashboard_url).hostname;
|
const rpID = (() => {
|
||||||
|
const url = new URL(config.getRawConfig().app.dashboard_url);
|
||||||
|
// For localhost, we must use 'localhost' without port
|
||||||
|
if (url.hostname === 'localhost') {
|
||||||
|
return 'localhost';
|
||||||
|
}
|
||||||
|
return url.hostname;
|
||||||
|
})();
|
||||||
|
|
||||||
const rpName = "Pangolin";
|
const rpName = "Pangolin";
|
||||||
const origin = config.getRawConfig().app.dashboard_url;
|
const origin = config.getRawConfig().app.dashboard_url;
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,14 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
||||||
|
|
||||||
async function loginWithSecurityKey() {
|
async function loginWithSecurityKey() {
|
||||||
try {
|
try {
|
||||||
|
// Check browser compatibility first
|
||||||
|
if (!window.PublicKeyCredential) {
|
||||||
|
setError(t('securityKeyBrowserNotSupported', {
|
||||||
|
defaultValue: "Your browser doesn't support security keys. Please use a modern browser like Chrome, Firefox, or Safari."
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
@ -203,29 +211,49 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
||||||
const { tempSessionId, ...options } = startRes.data.data;
|
const { tempSessionId, ...options } = startRes.data.data;
|
||||||
|
|
||||||
// Perform WebAuthn authentication
|
// Perform WebAuthn authentication
|
||||||
const credential = await startAuthentication(options);
|
try {
|
||||||
|
const credential = await startAuthentication(options);
|
||||||
|
|
||||||
|
// Verify authentication
|
||||||
|
const verifyRes = await api.post(
|
||||||
|
"/auth/passkey/authenticate/verify",
|
||||||
|
{ credential },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-Temp-Session-Id': tempSessionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Verify authentication
|
if (verifyRes) {
|
||||||
const verifyRes = await api.post(
|
if (onLogin) {
|
||||||
"/auth/passkey/authenticate/verify",
|
await onLogin();
|
||||||
{ credential },
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'X-Temp-Session-Id': tempSessionId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
} catch (error: any) {
|
||||||
|
if (error.name === 'NotAllowedError') {
|
||||||
if (verifyRes) {
|
if (error.message.includes('denied permission')) {
|
||||||
if (onLogin) {
|
setError(t('securityKeyPermissionDenied', {
|
||||||
await onLogin();
|
defaultValue: "Please allow access to your security key to continue signing in."
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setError(t('securityKeyRemovedTooQuickly', {
|
||||||
|
defaultValue: "Please keep your security key connected until the sign-in process completes."
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else if (error.name === 'NotSupportedError') {
|
||||||
|
setError(t('securityKeyNotSupported', {
|
||||||
|
defaultValue: "Your security key may not be compatible. Please try a different security key."
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setError(t('securityKeyUnknownError', {
|
||||||
|
defaultValue: "There was a problem using your security key. Please try again."
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
throw error; // Re-throw to be caught by outer catch
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(formatAxiosError(e));
|
||||||
setError(formatAxiosError(e, t('securityKeyAuthError', {
|
|
||||||
defaultValue: "Security key authentication failed"
|
|
||||||
})));
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,17 @@ export default function SecurityKeyForm({ open, setOpen }: SecurityKeyFormProps)
|
||||||
|
|
||||||
const handleRegisterSecurityKey = async (values: RegisterFormValues) => {
|
const handleRegisterSecurityKey = async (values: RegisterFormValues) => {
|
||||||
try {
|
try {
|
||||||
|
// Check browser compatibility first
|
||||||
|
if (!window.PublicKeyCredential) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: t('securityKeyBrowserNotSupported', {
|
||||||
|
defaultValue: "Your browser doesn't support security keys. Please use a modern browser like Chrome, Firefox, or Safari."
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsRegistering(true);
|
setIsRegistering(true);
|
||||||
const startRes = await api.post("/auth/passkey/register/start", {
|
const startRes = await api.post("/auth/passkey/register/start", {
|
||||||
name: values.name,
|
name: values.name,
|
||||||
|
@ -129,29 +140,72 @@ export default function SecurityKeyForm({ open, setOpen }: SecurityKeyFormProps)
|
||||||
if (startRes.status === 202) {
|
if (startRes.status === 202) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
description: "Two-factor authentication is required to register a security key.",
|
description: t('twoFactorRequired', {
|
||||||
|
defaultValue: "Two-factor authentication is required to register a security key."
|
||||||
|
})
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = startRes.data.data;
|
const options = startRes.data.data;
|
||||||
const credential = await startRegistration(options);
|
|
||||||
|
try {
|
||||||
|
const credential = await startRegistration(options);
|
||||||
|
|
||||||
await api.post("/auth/passkey/register/verify", {
|
await api.post("/auth/passkey/register/verify", {
|
||||||
credential,
|
credential,
|
||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
description: t('securityKeyRegisterSuccess')
|
description: t('securityKeyRegisterSuccess', {
|
||||||
});
|
defaultValue: "Security key registered successfully"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
registerForm.reset();
|
registerForm.reset();
|
||||||
setShowRegisterDialog(false);
|
setShowRegisterDialog(false);
|
||||||
await loadSecurityKeys();
|
await loadSecurityKeys();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name === 'NotAllowedError') {
|
||||||
|
if (error.message.includes('denied permission')) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: t('securityKeyPermissionDenied', {
|
||||||
|
defaultValue: "Please allow access to your security key to continue registration."
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: t('securityKeyRemovedTooQuickly', {
|
||||||
|
defaultValue: "Please keep your security key connected until the registration process completes."
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (error.name === 'NotSupportedError') {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: t('securityKeyNotSupported', {
|
||||||
|
defaultValue: "Your security key may not be compatible. Please try a different security key."
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: t('securityKeyUnknownError', {
|
||||||
|
defaultValue: "There was a problem registering your security key. Please try again."
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw error; // Re-throw to be caught by outer catch
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Security key registration error:', error);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
description: formatAxiosError(error, t('securityKeyRegisterError')),
|
description: formatAxiosError(error, t('securityKeyRegisterError', {
|
||||||
|
defaultValue: "Failed to register security key"
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsRegistering(false);
|
setIsRegistering(false);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue