diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index a84d4ee..ba7245a 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -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 diff --git a/apps/api/src/mailer/templates/confirm-2fa-recovery.hbs b/apps/api/src/mailer/templates/confirm-2fa-recovery.hbs new file mode 100644 index 0000000..cd6da73 --- /dev/null +++ b/apps/api/src/mailer/templates/confirm-2fa-recovery.hbs @@ -0,0 +1,14 @@ + + + 2FA Recovery + + +

Two-Factor Authentication Recovery

+

Hello, {{name}}

+

To recover your account, please click on the link below:

+ Recover Account +

If you did not request this, please ignore this email.

+ + diff --git a/apps/api/src/mailer/types/mailer.types.ts b/apps/api/src/mailer/types/mailer.types.ts index 89e7c69..127efbb 100644 --- a/apps/api/src/mailer/types/mailer.types.ts +++ b/apps/api/src/mailer/types/mailer.types.ts @@ -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' } diff --git a/apps/platform/src/hooks/use2FA.ts b/apps/platform/src/hooks/use2FA.ts index 9812636..c3956e0 100644 --- a/apps/platform/src/hooks/use2FA.ts +++ b/apps/platform/src/hooks/use2FA.ts @@ -4,7 +4,9 @@ import { Verify2FAData, turnOn2FAServiceStep1, turnOn2FAStep2Service, - auth2FAService + auth2FAService, + authDisableService, + Recover2FAData } from '@isomera/impl' import useSession from './useSession' @@ -32,11 +34,23 @@ const useTwoFactorAuthHook = () => { return response }) + const { mutateAsync: disable2FA, isLoading: isLoadingDisable } = useMutation( + async (data: Pure) => { + const response = await authDisableService(data) + return response + } + ) + return { generate2FA, verify2FA, authenticate, - isLoading: isLoadingGenerate || isLoadingVerify || isLoadingAuthenticate + disable2FA, + isLoading: + isLoadingGenerate || + isLoadingVerify || + isLoadingAuthenticate || + isLoadingDisable } } diff --git a/apps/platform/src/router/router.tsx b/apps/platform/src/router/router.tsx index 9dac6e5..d4d8af0 100644 --- a/apps/platform/src/router/router.tsx +++ b/apps/platform/src/router/router.tsx @@ -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 ( @@ -98,7 +99,7 @@ function Router() { - + I don't have access to my 2fa and need to reset with recovery code @@ -108,7 +109,7 @@ function Router() { /> @@ -118,6 +119,17 @@ function Router() { } /> + + + + + + } + /> + { + 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 ( +
+ {isLoading ?

{message}

:

Disabling 2FA, please wait...

} +
+ ) +} diff --git a/apps/platform/src/views/auth/recovery.view.tsx b/apps/platform/src/views/auth/recovery.view.tsx index b9daa3c..0a2d241 100644 --- a/apps/platform/src/views/auth/recovery.view.tsx +++ b/apps/platform/src/views/auth/recovery.view.tsx @@ -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() @@ -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) } diff --git a/libs/impl/src/constants/apiRoutes.ts b/libs/impl/src/constants/apiRoutes.ts index 3357cbd..665f3f2 100644 --- a/libs/impl/src/constants/apiRoutes.ts +++ b/libs/impl/src/constants/apiRoutes.ts @@ -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 diff --git a/libs/impl/src/constants/pages.ts b/libs/impl/src/constants/pages.ts index a96c6dd..11577c6 100644 --- a/libs/impl/src/constants/pages.ts +++ b/libs/impl/src/constants/pages.ts @@ -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' diff --git a/libs/impl/src/services/auth/twoFactor.service.ts b/libs/impl/src/services/auth/twoFactor.service.ts index 4d3e388..94461aa 100644 --- a/libs/impl/src/services/auth/twoFactor.service.ts +++ b/libs/impl/src/services/auth/twoFactor.service.ts @@ -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, @@ -65,3 +66,11 @@ export const auth2FARecoveryService = async ( return response.data } + +export const authDisableService = async ( + data: Verify2FAData +): Promise => { + const response = await axiosInstance.post(API_AUTH_2FA_CONFIRM_RECOVERY, data) + + return response.data +}