MFA: display meaningful errors

This commit is contained in:
gbuomprisco
2024-10-12 04:35:03 +02:00
parent 1ee6d8c669
commit f2b74a9c7e
5 changed files with 60 additions and 25 deletions

View File

@@ -150,14 +150,25 @@ function ConfirmUnenrollFactorModal(
(factorId: string) => {
if (unEnroll.isPending) return;
const promise = unEnroll.mutateAsync(factorId).then(() => {
const promise = unEnroll.mutateAsync(factorId).then((response) => {
props.setIsModalOpen(false);
if (!response.success) {
const errorCode = response.data;
throw t(`auth:errors.${errorCode}`, {
defaultValue: t(`account:unenrollFactorError`)
});
}
});
toast.promise(promise, {
loading: t(`account:unenrollingFactor`),
success: t(`account:unenrollFactorSuccess`),
error: t(`account:unenrollFactorError`),
error: (error: string) => {
return error;
},
duration: Infinity
});
},
[props, t, unEnroll],
@@ -279,16 +290,22 @@ function useUnenrollFactor(userId: string) {
});
if (error) {
throw error;
return {
success: false as const,
data: error.code as string,
}
}
return data;
return {
success: true as const,
data,
}
};
return useMutation({
mutationFn,
mutationKey,
onSuccess: async () => {
onSuccess: () => {
return queryClient.refetchQueries({
queryKey: mutationKey,
});

View File

@@ -6,7 +6,7 @@ import Image from 'next/image';
import { zodResolver } from '@hookform/resolvers/zod';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
@@ -43,6 +43,7 @@ import {
import { Trans } from '@kit/ui/trans';
import { refreshAuthSession } from '../../../server/personal-accounts-server-actions';
import {ArrowLeftIcon} from "lucide-react";
export function MultiFactorAuthSetupDialog(props: { userId: string }) {
const { t } = useTranslation();
@@ -252,7 +253,8 @@ function FactorQrCode({
onSetFactorId: (factorId: string) => void;
}>) {
const enrollFactorMutation = useEnrollFactor(userId);
const [error, setError] = useState(false);
const { t } = useTranslation();
const [error, setError] = useState<string>('');
const form = useForm({
resolver: zodResolver(
@@ -280,9 +282,16 @@ function FactorQrCode({
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'account:qrCodeErrorDescription'} />
<Trans i18nKey={`auth:errors.${error}`} defaults={t('account:qrCodeErrorDescription')} />
</AlertDescription>
</Alert>
<div>
<Button variant={'outline'} onClick={onCancel}>
<ArrowLeftIcon className={'h-4'} />
<Trans i18nKey={`common:retry`} />
</Button>
</div>
</div>
);
}
@@ -292,18 +301,14 @@ function FactorQrCode({
<FactorNameForm
onCancel={onCancel}
onSetFactorName={async (name) => {
const data = await enrollFactorMutation
.mutateAsync(name)
.catch((error) => {
console.error(error);
const response = await enrollFactorMutation.mutateAsync(name);
return;
});
if (data === undefined) {
return setError(true);
if (!response.success) {
return setError(response.data as string);
}
const data = response.data;
if (data.type === 'totp') {
form.setValue('factorName', name);
form.setValue('qrCode', data.totp.qr_code);
@@ -401,24 +406,36 @@ function QrImage({ src }: { src: string }) {
function useEnrollFactor(userId: string) {
const client = useSupabase();
const queryClient = useQueryClient();
const mutationKey = useFactorsMutationKey(userId);
const mutationFn = async (factorName: string) => {
const { data, error } = await client.auth.mfa.enroll({
const response = await client.auth.mfa.enroll({
friendlyName: factorName,
factorType: 'totp',
});
if (error) {
throw error;
if (response.error) {
return {
success: false as const,
data: response.error.code,
}
}
return data;
return {
success: true as const,
data: response.data,
}
};
return useMutation({
mutationFn,
mutationKey,
onSuccess() {
return queryClient.refetchQueries({
queryKey: mutationKey,
});
},
});
}