import { useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; import { flushSync } from 'react-dom'; import { useForm } from 'react-hook-form'; import { match } from 'ts-pattern'; import { renderSVG } from 'uqr'; import { z } from 'zod'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@documenso/ui/primitives/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { RecoveryCodeList } from './recovery-code-list'; export const ZSetupTwoFactorAuthenticationForm = z.object({ password: z.string().min(6).max(72), }); export type TSetupTwoFactorAuthenticationForm = z.infer; export const ZEnableTwoFactorAuthenticationForm = z.object({ token: z.string(), }); export type TEnableTwoFactorAuthenticationForm = z.infer; export type EnableAuthenticatorAppDialogProps = { open: boolean; onOpenChange: (_open: boolean) => void; }; export const EnableAuthenticatorAppDialog = ({ open, onOpenChange, }: EnableAuthenticatorAppDialogProps) => { const router = useRouter(); const { toast } = useToast(); const { mutateAsync: setupTwoFactorAuthentication, data: setupTwoFactorAuthenticationData } = trpc.twoFactorAuthentication.setup.useMutation(); const { mutateAsync: enableTwoFactorAuthentication, data: enableTwoFactorAuthenticationData } = trpc.twoFactorAuthentication.enable.useMutation(); const setupTwoFactorAuthenticationForm = useForm({ defaultValues: { password: '', }, resolver: zodResolver(ZSetupTwoFactorAuthenticationForm), }); const { isSubmitting: isSetupTwoFactorAuthenticationSubmitting } = setupTwoFactorAuthenticationForm.formState; const enableTwoFactorAuthenticationForm = useForm({ defaultValues: { token: '', }, resolver: zodResolver(ZEnableTwoFactorAuthenticationForm), }); const { isSubmitting: isEnableTwoFactorAuthenticationSubmitting } = enableTwoFactorAuthenticationForm.formState; const step = useMemo(() => { if (!setupTwoFactorAuthenticationData || isSetupTwoFactorAuthenticationSubmitting) { return 'setup'; } if (!enableTwoFactorAuthenticationData || isEnableTwoFactorAuthenticationSubmitting) { return 'enable'; } return 'view'; }, [ setupTwoFactorAuthenticationData, isSetupTwoFactorAuthenticationSubmitting, enableTwoFactorAuthenticationData, isEnableTwoFactorAuthenticationSubmitting, ]); const onSetupTwoFactorAuthenticationFormSubmit = async ({ password, }: TSetupTwoFactorAuthenticationForm) => { try { await setupTwoFactorAuthentication({ password }); } catch (_err) { toast({ title: 'Unable to setup two-factor authentication', description: 'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.', variant: 'destructive', }); } }; const onEnableTwoFactorAuthenticationFormSubmit = async ({ token, }: TEnableTwoFactorAuthenticationForm) => { try { await enableTwoFactorAuthentication({ code: token }); toast({ title: 'Two-factor authentication enabled', description: 'Two-factor authentication has been enabled for your account. You will now be required to enter a code from your authenticator app when signing in.', }); } catch (_err) { toast({ title: 'Unable to setup two-factor authentication', description: 'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.', variant: 'destructive', }); } }; const onCompleteClick = () => { flushSync(() => { onOpenChange(false); }); router.refresh(); }; return ( Enable Authenticator App {step === 'setup' && ( To enable two-factor authentication, please enter your password below. )} {step === 'view' && ( Your recovery codes are listed below. Please store them in a safe place. )} {match(step) .with('setup', () => { return (
( Password )} /> ); }) .with('enable', () => (

To enable two-factor authentication, scan the following QR code using your authenticator app.

If your authenticator app does not support QR codes, you can use the following code instead:

{setupTwoFactorAuthenticationData?.secret}

Once you have scanned the QR code or entered the code manually, enter the code provided by your authenticator app below.

( Token )} /> )) .with('view', () => (
{enableTwoFactorAuthenticationData?.recoveryCodes && ( )}
)) .exhaustive()}
); };