Skip to content

Commit

Permalink
refactor: Enhance two-factor authentication ux/flow with improved err…
Browse files Browse the repository at this point in the history
…or handling and loading state management, prevent redirect to /login
  • Loading branch information
danny-avila committed Feb 17, 2025
1 parent 59e6256 commit be1cb7a
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 40 deletions.
45 changes: 26 additions & 19 deletions client/src/components/Auth/TwoFactorScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { useSearchParams } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form';
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp';
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Label } from '~/components';
import { useLocalize } from '~/hooks';
import { useVerifyTwoFactorTempMutation } from '~/data-provider';
import { useToastContext } from '~/Providers';
import { useLocalize } from '~/hooks';

interface VerifyPayload {
tempToken: string;
Expand All @@ -28,8 +29,28 @@ const TwoFactorScreen: React.FC = React.memo(() => {
formState: { errors },
} = useForm<TwoFactorFormInputs>();
const localize = useLocalize();
const { showToast } = useToastContext();
const [useBackup, setUseBackup] = useState<boolean>(false);
const { mutate: verifyTempMutate, isLoading } = useVerifyTwoFactorTempMutation();
const [isLoading, setIsLoading] = useState<boolean>(false);
const { mutate: verifyTempMutate } = useVerifyTwoFactorTempMutation({
onSuccess: (result) => {
if (result.token != null && result.token !== '') {
window.location.href = '/';
}
},
onMutate: () => {
setIsLoading(true);
},
onError: (error: unknown) => {
setIsLoading(false);
const err = error as { response?: { data?: { message?: unknown } } };
const errorMsg =
typeof err.response?.data?.message === 'string'
? err.response.data.message
: 'Error verifying 2FA';
showToast({ message: errorMsg, status: 'error' });
},
});

const onSubmit = useCallback(
(data: TwoFactorFormInputs) => {
Expand All @@ -39,21 +60,7 @@ const TwoFactorScreen: React.FC = React.memo(() => {
} else if (data.token != null && data.token !== '') {
payload.token = data.token;
}
verifyTempMutate(payload, {
onSuccess: (result) => {
if (result.token != null && result.token !== '') {
window.location.href = '/';
}
},
onError: (error: unknown) => {
const err = error as { response?: { data?: { message?: unknown } } };
const errorMsg =
typeof err.response?.data?.message === 'string'
? err.response.data.message
: 'Error verifying 2FA';
alert(errorMsg);
},
});
verifyTempMutate(payload);
},
[tempToken, useBackup, verifyTempMutate],
);
Expand Down Expand Up @@ -133,13 +140,13 @@ const TwoFactorScreen: React.FC = React.memo(() => {
)}
<div className="flex items-center justify-between">
<button
type="submit"
aria-label={localize('com_auth_continue')}
data-testid="login-button"
type="submit"
disabled={isLoading}
className="w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-80 dark:bg-green-600 dark:hover:bg-green-700"
>
{isLoading ? 'Verifying...' : 'Verify'}
{isLoading ? localize('com_auth_email_verifying_ellipsis') : localize('com_ui_verify')}
</button>
</div>
<div className="mt-4 flex justify-center">
Expand Down
35 changes: 14 additions & 21 deletions client/src/data-provider/Auth/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,11 @@ export const useVerifyTwoFactorMutation = (): UseMutationResult<
unknown
> => {
const queryClient = useQueryClient();
return useMutation(
(payload: t.TVerify2FARequest) => dataService.verifyTwoFactor(payload),
{
onSuccess: (data) => {
queryClient.setQueryData([QueryKeys.user, '2fa'], data);
},
return useMutation((payload: t.TVerify2FARequest) => dataService.verifyTwoFactor(payload), {
onSuccess: (data) => {
queryClient.setQueryData([QueryKeys.user, '2fa'], data);
},
);
});
};

export const useConfirmTwoFactorMutation = (): UseMutationResult<
Expand All @@ -125,14 +122,11 @@ export const useConfirmTwoFactorMutation = (): UseMutationResult<
unknown
> => {
const queryClient = useQueryClient();
return useMutation(
(payload: t.TVerify2FARequest) => dataService.confirmTwoFactor(payload),
{
onSuccess: (data) => {
queryClient.setQueryData([QueryKeys.user, '2fa'], data);
},
return useMutation((payload: t.TVerify2FARequest) => dataService.confirmTwoFactor(payload), {
onSuccess: (data) => {
queryClient.setQueryData([QueryKeys.user, '2fa'], data);
},
);
});
};

export const useDisableTwoFactorMutation = (): UseMutationResult<
Expand Down Expand Up @@ -163,18 +157,17 @@ export const useRegenerateBackupCodesMutation = (): UseMutationResult<
});
};

export const useVerifyTwoFactorTempMutation = (): UseMutationResult<
t.TVerify2FATempResponse,
unknown,
t.TVerify2FATempRequest,
unknown
> => {
export const useVerifyTwoFactorTempMutation = (
options?: t.MutationOptions<t.TVerify2FATempResponse, t.TVerify2FATempRequest, unknown, unknown>,
): UseMutationResult<t.TVerify2FATempResponse, unknown, t.TVerify2FATempRequest, unknown> => {
const queryClient = useQueryClient();
return useMutation(
(payload: t.TVerify2FATempRequest) => dataService.verifyTwoFactorTemp(payload),
{
onSuccess: (data) => {
...(options || {}),
onSuccess: (data, ...args) => {
queryClient.setQueryData([QueryKeys.user, '2fa'], data);
options?.onSuccess?.(data, ...args);
},
},
);
Expand Down
1 change: 1 addition & 0 deletions client/src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"com_auth_email_verification_redirecting": "Redirecting in {{0}} seconds...",
"com_auth_email_verification_resend_prompt": "Didn't receive the email?",
"com_auth_email_verification_success": "Email verified successfully",
"com_auth_email_verifying_ellipsis": "Verifying...",
"com_auth_error_create": "There was an error attempting to register your account. Please try again.",
"com_auth_error_invalid_reset_token": "This password reset token is no longer valid.",
"com_auth_error_login": "Unable to login with the information provided. Please check your credentials and try again.",
Expand Down
3 changes: 3 additions & 0 deletions packages/data-provider/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ axios.interceptors.response.use(
return Promise.reject(error);
}

if (originalRequest.url?.includes('/api/auth/2fa') === true) {
return Promise.reject(error);
}
if (originalRequest.url?.includes('/api/auth/logout') === true) {
return Promise.reject(error);
}
Expand Down

0 comments on commit be1cb7a

Please sign in to comment.