Skip to content

Commit

Permalink
Update disable 2fa email and flow
Browse files Browse the repository at this point in the history
  • Loading branch information
PhamAnhHoang committed Feb 9, 2024
1 parent 4a219fd commit ecde1cf
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 11 deletions.
8 changes: 5 additions & 3 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,13 @@ export class AuthService {
if (code.code) {
await this.mailerService.sendEmail(
user,
'Email verification',
HandlebarsTemplate.EMAIL_CONFIRMATION,
'Two Factor Authentication Recovery',
HandlebarsTemplate.CONFIRM_RECOVERY,
{
name: user.firstName,
code: code.code
code: code.code,
email: user.email,
baseUrl: process.env.PLATFORM_URL
}
)
return user
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/mailer/templates/confirm-2fa-recovery.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<title>2FA Recovery</title>
</head>
<body>
<h1>Two-Factor Authentication Recovery</h1>
<p>Hello, {{name}}</p>
<p>To recover your account, please click on the link below:</p>
<a
href='{{baseUrl}}/2fa/confirm-recovery?email={{email}}&code={{code}}'
>Recover Account</a>
<p>If you did not request this, please ignore this email.</p>
</body>
</html>
3 changes: 2 additions & 1 deletion apps/api/src/mailer/types/mailer.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum HandlebarsTemplate {
PASSWORD_RESET_CODE = 'password-reset-code',
WELCOME = 'welcome',
EMAIL_CONFIRMATION = 'email-confirmation'
EMAIL_CONFIRMATION = 'email-confirmation',
CONFIRM_RECOVERY = 'confirm-2fa-recovery'
}
18 changes: 16 additions & 2 deletions apps/platform/src/hooks/use2FA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
Verify2FAData,
turnOn2FAServiceStep1,
turnOn2FAStep2Service,
auth2FAService
auth2FAService,
authDisableService,
Recover2FAData
} from '@isomera/impl'
import useSession from './useSession'

Expand Down Expand Up @@ -32,11 +34,23 @@ const useTwoFactorAuthHook = () => {
return response
})

const { mutateAsync: disable2FA, isLoading: isLoadingDisable } = useMutation(
async (data: Pure<Recover2FAData>) => {
const response = await authDisableService(data)
return response
}
)

return {
generate2FA,
verify2FA,
authenticate,
isLoading: isLoadingGenerate || isLoadingVerify || isLoadingAuthenticate
disable2FA,
isLoading:
isLoadingGenerate ||
isLoadingVerify ||
isLoadingAuthenticate ||
isLoadingDisable
}
}

Expand Down
16 changes: 14 additions & 2 deletions apps/platform/src/router/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { PrivateLayout } from '../layouts/private.layout'
import { UserSecurityView } from '../views/user /profile-security.view'
import { Verify2FAView } from '../views/auth/auth2FA.view'
import { Recovery2FAView } from '../views/auth/recovery.view'
import { Disable2FAView } from '../views/auth/disable-2fa.view'

function Router() {
return (
Expand Down Expand Up @@ -98,7 +99,7 @@ function Router() {
<PublicRoute>
<PublicLayout>
<Verify2FAView />
<Link to={pages.recoverTwoFA.path}>
<Link to={pages.recover2FA.path}>
I don&apos;t have access to my 2fa and need to reset with
recovery code
</Link>
Expand All @@ -108,7 +109,7 @@ function Router() {
/>

<Route
path={pages.recoverTwoFA.path}
path={pages.recover2FA.path}
element={
<PublicRoute>
<PublicLayout>
Expand All @@ -118,6 +119,17 @@ function Router() {
}
/>

<Route
path={pages.confirmRecovery2FA.path}
element={
<PublicRoute>
<PublicLayout>
<Disable2FAView />
</PublicLayout>
</PublicRoute>
}
/>

<Route
path={pages.userInfo.path}
element={
Expand Down
40 changes: 40 additions & 0 deletions apps/platform/src/views/auth/disable-2fa.view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState, useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useTwoFactorAuthHook } from '../../hooks/use2FA'
import { toast } from 'react-toastify'

export const Disable2FAView = () => {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const [message, setMessage] = useState('')

const { disable2FA, isLoading } = useTwoFactorAuthHook()

useEffect(() => {
const email = searchParams.get('email')
const code = searchParams.get('code')

if (!email || !code) {
navigate('/login')
} else {
disable2FA({ email, code })
.then(() => {
const msg =
'Your 2fa was disabled, please log-in and enable 2fa again.'
toast.success(msg)
setMessage(msg)
setTimeout(() => navigate('/login'), 3000)
})
.catch((error: any) => {
console.error('Error disabling 2FA:', error)
navigate('/login')
})
}
}, [disable2FA, navigate, searchParams])

return (
<div>
{isLoading ? <p>{message}</p> : <p>Disabling 2FA, please wait...</p>}
</div>
)
}
2 changes: 2 additions & 0 deletions apps/platform/src/views/auth/recovery.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useNavigate } from 'react-router-dom'
import useSession from '../../hooks/useSession'
import { pages, useHandleErrorHook } from '@isomera/impl'
import { useRecoveryHook } from '../../hooks/Recover2FAHook'
import { toast } from 'react-toastify'

export const Recovery2FAView = () => {
const { loginWith2FA, user } = useSession()
Expand All @@ -10,6 +11,7 @@ export const Recovery2FAView = () => {

const onSuccess = (data: { access_token: string; refresh_token: string }) => {
loginWith2FA(data.access_token, data.refresh_token)
toast.success('Check your email for further recovery steps.')
navigate(pages.dashboard.path)
}

Expand Down
1 change: 1 addition & 0 deletions libs/impl/src/constants/apiRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const API_AUTH_2FA_STEP_1 = 'auth/2fa/generate'
export const API_AUTH_2FA_STEP_2 = 'auth/2fa/turn-on'
export const API_AUTH_2FA_STEP_3 = 'auth/2fa/authenticate'
export const API_AUTH_2FA_RECOVER = 'auth/2fa/request-recovery'
export const API_AUTH_2FA_CONFIRM_RECOVERY = 'auth/2fa/confirm-recovery'

// User

Expand Down
9 changes: 6 additions & 3 deletions libs/impl/src/constants/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ export const pages = {
path: '/login'
},
twoFA: {
path: '/two-fa'
path: '/2fa'
},
recoverTwoFA: {
path: '/two-fa/recover'
recover2FA: {
path: '/2fa/recover'
},
confirmRecovery2FA: {
path: '/2fa/confirm-recovery'
},
register: {
path: '/sign-up'
Expand Down
9 changes: 9 additions & 0 deletions libs/impl/src/services/auth/twoFactor.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { UserInterface } from '@isomera/interfaces'
import {
API_AUTH_2FA_CONFIRM_RECOVERY,
API_AUTH_2FA_RECOVER,
API_AUTH_2FA_STEP_1,
API_AUTH_2FA_STEP_2,
Expand Down Expand Up @@ -65,3 +66,11 @@ export const auth2FARecoveryService = async (

return response.data
}

export const authDisableService = async (
data: Verify2FAData
): Promise<Auth2FAResponse> => {
const response = await axiosInstance.post(API_AUTH_2FA_CONFIRM_RECOVERY, data)

return response.data
}

0 comments on commit ecde1cf

Please sign in to comment.