2024-03-25 11:34:50 +08:00
import { useState } from 'react' ;
2023-12-01 05:52:16 +05:30
import { zodResolver } from '@hookform/resolvers/zod' ;
2025-01-02 15:33:37 +11:00
import { msg } from '@lingui/core/macro' ;
2024-08-27 20:34:39 +09:00
import { useLingui } from '@lingui/react' ;
2025-01-02 15:33:37 +11:00
import { Trans } from '@lingui/react/macro' ;
2023-12-01 05:52:16 +05:30
import { flushSync } from 'react-dom' ;
import { useForm } from 'react-hook-form' ;
2025-01-02 15:33:37 +11:00
import { useRevalidator } from 'react-router' ;
2023-12-01 05:52:16 +05:30
import { z } from 'zod' ;
import { trpc } from '@documenso/trpc/react' ;
import { Button } from '@documenso/ui/primitives/button' ;
import {
Dialog ,
DialogContent ,
DialogDescription ,
2024-01-30 17:31:27 +11:00
DialogFooter ,
2023-12-01 05:52:16 +05:30
DialogHeader ,
DialogTitle ,
2024-03-25 11:34:50 +08:00
DialogTrigger ,
2023-12-01 05:52:16 +05:30
} from '@documenso/ui/primitives/dialog' ;
import {
Form ,
FormControl ,
FormField ,
FormItem ,
2024-08-29 01:00:57 +00:00
FormLabel ,
2023-12-01 05:52:16 +05:30
FormMessage ,
} from '@documenso/ui/primitives/form/form' ;
2024-08-29 01:00:57 +00:00
import { Input } from '@documenso/ui/primitives/input' ;
2024-05-24 03:31:19 +00:00
import { PinInput , PinInputGroup , PinInputSlot } from '@documenso/ui/primitives/pin-input' ;
2023-12-01 05:52:16 +05:30
import { useToast } from '@documenso/ui/primitives/use-toast' ;
2024-03-25 11:34:50 +08:00
export const ZDisable2FAForm = z . object ( {
2024-08-29 01:00:57 +00:00
totpCode : z.string ( ) . trim ( ) . optional ( ) ,
backupCode : z.string ( ) . trim ( ) . optional ( ) ,
2023-12-01 05:52:16 +05:30
} ) ;
2024-03-25 11:34:50 +08:00
export type TDisable2FAForm = z . infer < typeof ZDisable2FAForm > ;
2023-12-01 05:52:16 +05:30
2024-03-25 11:34:50 +08:00
export const DisableAuthenticatorAppDialog = ( ) = > {
2024-08-27 20:34:39 +09:00
const { _ } = useLingui ( ) ;
2023-12-01 05:52:16 +05:30
const { toast } = useToast ( ) ;
2025-01-02 15:33:37 +11:00
const { revalidate } = useRevalidator ( ) ;
2023-12-01 05:52:16 +05:30
2024-03-25 11:34:50 +08:00
const [ isOpen , setIsOpen ] = useState ( false ) ;
2024-08-29 01:00:57 +00:00
const [ twoFactorDisableMethod , setTwoFactorDisableMethod ] = useState < 'totp' | 'backup' > ( 'totp' ) ;
2024-03-25 11:34:50 +08:00
const { mutateAsync : disable2FA } = trpc . twoFactorAuthentication . disable . useMutation ( ) ;
2023-12-01 05:52:16 +05:30
2024-03-25 11:34:50 +08:00
const disable2FAForm = useForm < TDisable2FAForm > ( {
2023-12-01 05:52:16 +05:30
defaultValues : {
2024-08-29 01:00:57 +00:00
totpCode : '' ,
backupCode : '' ,
2023-12-01 05:52:16 +05:30
} ,
2024-03-25 11:34:50 +08:00
resolver : zodResolver ( ZDisable2FAForm ) ,
2023-12-01 05:52:16 +05:30
} ) ;
2024-08-29 01:00:57 +00:00
const onCloseTwoFactorDisableDialog = ( ) = > {
disable2FAForm . reset ( ) ;
setIsOpen ( ! isOpen ) ;
} ;
const onToggleTwoFactorDisableMethodClick = ( ) = > {
const method = twoFactorDisableMethod === 'totp' ? 'backup' : 'totp' ;
if ( method === 'totp' ) {
disable2FAForm . setValue ( 'backupCode' , '' ) ;
}
if ( method === 'backup' ) {
disable2FAForm . setValue ( 'totpCode' , '' ) ;
}
setTwoFactorDisableMethod ( method ) ;
} ;
2024-03-25 11:34:50 +08:00
const { isSubmitting : isDisable2FASubmitting } = disable2FAForm . formState ;
2023-12-01 05:52:16 +05:30
2024-08-29 01:00:57 +00:00
const onDisable2FAFormSubmit = async ( { totpCode , backupCode } : TDisable2FAForm ) = > {
2023-12-01 05:52:16 +05:30
try {
2024-08-29 01:00:57 +00:00
await disable2FA ( { totpCode , backupCode } ) ;
2023-12-01 05:52:16 +05:30
toast ( {
2024-08-27 20:34:39 +09:00
title : _ ( msg ` Two-factor authentication disabled ` ) ,
description : _ (
msg ` Two-factor authentication has been disabled for your account. You will no longer be required to enter a code from your authenticator app when signing in. ` ,
) ,
2023-12-01 05:52:16 +05:30
} ) ;
flushSync ( ( ) = > {
2024-08-29 01:00:57 +00:00
onCloseTwoFactorDisableDialog ( ) ;
2023-12-01 05:52:16 +05:30
} ) ;
2025-01-02 15:33:37 +11:00
await revalidate ( ) ;
2023-12-01 05:52:16 +05:30
} catch ( _err ) {
toast ( {
2024-08-27 20:34:39 +09:00
title : _ ( msg ` Unable to disable two-factor authentication ` ) ,
description : _ (
msg ` We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again. ` ,
) ,
2023-12-01 05:52:16 +05:30
variant : 'destructive' ,
} ) ;
}
} ;
return (
2024-08-29 01:00:57 +00:00
< Dialog open = { isOpen } onOpenChange = { onCloseTwoFactorDisableDialog } >
2024-03-25 11:34:50 +08:00
< DialogTrigger asChild = { true } >
< Button className = "flex-shrink-0" variant = "destructive" >
2024-08-27 20:34:39 +09:00
< Trans > Disable 2 FA < / Trans >
2024-03-25 11:34:50 +08:00
< / Button >
< / DialogTrigger >
< DialogContent position = "center" >
2023-12-01 05:52:16 +05:30
< DialogHeader >
2024-08-27 20:34:39 +09:00
< DialogTitle >
< Trans > Disable 2 FA < / Trans >
< / DialogTitle >
2023-12-01 05:52:16 +05:30
< DialogDescription >
2024-08-27 20:34:39 +09:00
< Trans >
Please provide a token from the authenticator , or a backup code . If you do not have a
backup code available , please contact support .
< / Trans >
2023-12-01 05:52:16 +05:30
< / DialogDescription >
< / DialogHeader >
2024-03-25 11:34:50 +08:00
< Form { ...disable2FAForm } >
< form onSubmit = { disable2FAForm . handleSubmit ( onDisable2FAFormSubmit ) } >
< fieldset className = "flex flex-col gap-y-4" disabled = { isDisable2FASubmitting } >
2024-08-29 01:00:57 +00:00
{ twoFactorDisableMethod === 'totp' && (
< FormField
name = "totpCode"
control = { disable2FAForm . control }
render = { ( { field } ) = > (
< FormItem >
< FormControl >
< PinInput { ...field } value = { field . value ? ? '' } maxLength = { 6 } >
{ Array ( 6 )
. fill ( null )
. map ( ( _ , i ) = > (
< PinInputGroup key = { i } >
< PinInputSlot index = { i } / >
< / PinInputGroup >
) ) }
< / PinInput >
< / FormControl >
< FormMessage / >
< / FormItem >
) }
/ >
) }
{ twoFactorDisableMethod === 'backup' && (
< FormField
control = { disable2FAForm . control }
name = "backupCode"
render = { ( { field } ) = > (
< FormItem >
< FormLabel >
< Trans > Backup Code < / Trans >
< / FormLabel >
< FormControl >
< Input type = "text" { ...field } / >
< / FormControl >
< FormMessage / >
< / FormItem >
) }
/ >
) }
2023-12-02 17:54:19 +05:30
2024-03-25 11:34:50 +08:00
< DialogFooter >
2024-08-29 01:00:57 +00:00
< Button
type = "button"
variant = "secondary"
onClick = { onToggleTwoFactorDisableMethodClick }
>
{ twoFactorDisableMethod === 'totp' ? (
< Trans > Use Backup Code < / Trans >
) : (
< Trans > Use Authenticator < / Trans >
) }
< / Button >
2024-03-25 11:34:50 +08:00
< Button type = "submit" variant = "destructive" loading = { isDisable2FASubmitting } >
2024-08-27 20:34:39 +09:00
< Trans > Disable 2 FA < / Trans >
2024-03-25 11:34:50 +08:00
< / Button >
< / DialogFooter >
2023-12-02 17:54:19 +05:30
< / fieldset >
2023-12-01 05:52:16 +05:30
< / form >
< / Form >
< / DialogContent >
< / Dialog >
) ;
} ;