import { useEffect, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { useForm } from 'react-hook-form'; import { useRevalidator } from 'react-router'; 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, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@documenso/ui/primitives/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; import { PinInput, PinInputGroup, PinInputSlot } from '@documenso/ui/primitives/pin-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { RecoveryCodeList } from './recovery-code-list'; export const ZEnable2FAForm = z.object({ token: z.string(), }); export type TEnable2FAForm = z.infer; export type EnableAuthenticatorAppDialogProps = { onSuccess?: () => void; }; export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorAppDialogProps) => { const { _ } = useLingui(); const { toast } = useToast(); const { revalidate } = useRevalidator(); const [isOpen, setIsOpen] = useState(false); const [recoveryCodes, setRecoveryCodes] = useState(null); const { mutateAsync: enable2FA } = trpc.twoFactorAuthentication.enable.useMutation(); const { mutateAsync: setup2FA, data: setup2FAData, isPending: isSettingUp2FA, } = trpc.twoFactorAuthentication.setup.useMutation({ onError: () => { toast({ title: _(msg`Unable to setup two-factor authentication`), description: _( msg`We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.`, ), variant: 'destructive', }); }, }); const enable2FAForm = useForm({ defaultValues: { token: '', }, resolver: zodResolver(ZEnable2FAForm), }); const { isSubmitting: isEnabling2FA } = enable2FAForm.formState; const onEnable2FAFormSubmit = async ({ token }: TEnable2FAForm) => { try { const data = await enable2FA({ code: token }); setRecoveryCodes(data.recoveryCodes); onSuccess?.(); toast({ title: _(msg`Two-factor authentication enabled`), description: _( msg`You will now be required to enter a code from your authenticator app when signing in.`, ), }); } catch (_err) { toast({ title: _(msg`Unable to setup two-factor authentication`), description: _( msg`We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.`, ), variant: 'destructive', }); } }; const downloadRecoveryCodes = () => { if (recoveryCodes) { const blob = new Blob([recoveryCodes.join('\n')], { type: 'text/plain', }); downloadFile({ filename: 'documenso-2FA-recovery-codes.txt', data: blob, }); } }; const handleEnable2FA = async () => { if (!setup2FAData) { await setup2FA(); } setIsOpen(true); }; useEffect(() => { enable2FAForm.reset(); if (!isOpen && recoveryCodes && recoveryCodes.length > 0) { setRecoveryCodes(null); void revalidate(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); return ( {setup2FAData && ( <> {recoveryCodes ? (
Backup codes Your recovery codes are listed below. Please store them in a safe place.
) : (
Enable Authenticator App 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:

{setup2FAData?.secret}

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

( Token {Array(6) .fill(null) .map((_, i) => ( ))} )} />
)} )}
); };