import { useMemo } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { match } from 'ts-pattern'; import { renderSVG } from 'uqr'; import { z } from 'zod'; import { downloadFile } from '@documenso/lib/client-only/download-file'; 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 { toast } = useToast(); const { mutateAsync: setupTwoFactorAuthentication, data: setupTwoFactorAuthenticationData } = trpc.twoFactorAuthentication.setup.useMutation(); const { mutateAsync: enableTwoFactorAuthentication, data: enableTwoFactorAuthenticationData, isLoading: isEnableTwoFactorAuthenticationDataLoading, } = 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 downloadRecoveryCodes = () => { if (enableTwoFactorAuthenticationData && enableTwoFactorAuthenticationData.recoveryCodes) { const blob = new Blob([enableTwoFactorAuthenticationData.recoveryCodes.join('\n')], { type: 'text/plain', }); downloadFile({ filename: 'documenso-2FA-recovery-codes.txt', data: blob, }); } }; 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', }); } }; 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()}
); };