diff --git a/apps/platform/src/router/router.tsx b/apps/platform/src/router/router.tsx index d4d8af0..37dfdba 100644 --- a/apps/platform/src/router/router.tsx +++ b/apps/platform/src/router/router.tsx @@ -5,7 +5,7 @@ import { SignUpView } from '../views/auth/signUp.view' import { pages } from '@isomera/impl' import { PasswordResetView } from '../views/auth/passwordReset.view' import { PasswordResetConfirmView } from '../views/auth/passwordResetConfirm.view' -import { UserInfoView } from '../views/user /info.view' +import { UserInfoView } from '../views/user/info.view' import PublicRoute from './publicRoute' import { VerificationCodeView } from '../views/auth/verificationCode.view' @@ -13,7 +13,7 @@ import { Routes, Route } from 'react-router-dom' import PrivateRoute from './privateRoute' import { PublicLayout } from '../layouts/public.layout' import { PrivateLayout } from '../layouts/private.layout' -import { UserSecurityView } from '../views/user /profile-security.view' +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' diff --git a/apps/platform/src/views/user /info.view.tsx b/apps/platform/src/views/user/info.view.tsx similarity index 96% rename from apps/platform/src/views/user /info.view.tsx rename to apps/platform/src/views/user/info.view.tsx index b44ebe6..1d108c8 100644 --- a/apps/platform/src/views/user /info.view.tsx +++ b/apps/platform/src/views/user/info.view.tsx @@ -1,15 +1,15 @@ -import { Link } from 'react-router-dom' -import useSession from '../../hooks/useSession' - -export const UserInfoView = () => { - const { user } = useSession() - - return ( -
- Profile here -
First Name: {user?.firstName}
-
Last Name: {user?.lastName}
- Security -
- ) -} +import { Link } from 'react-router-dom' +import useSession from '../../hooks/useSession' + +export const UserInfoView = () => { + const { user } = useSession() + + return ( +
+ Profile here +
First Name: {user?.firstName}
+
Last Name: {user?.lastName}
+ Security +
+ ) +} diff --git a/apps/platform/src/views/user /profile-security.view.tsx b/apps/platform/src/views/user/profile-security.view.tsx similarity index 96% rename from apps/platform/src/views/user /profile-security.view.tsx rename to apps/platform/src/views/user/profile-security.view.tsx index 87df436..4417156 100644 --- a/apps/platform/src/views/user /profile-security.view.tsx +++ b/apps/platform/src/views/user/profile-security.view.tsx @@ -1,203 +1,203 @@ -import React, { useEffect, useState } from 'react' -import useSession from '../../hooks/useSession' -import { useTwoFactorAuthHook } from '../../hooks/use2FA' -import { setAuthState } from '@isomera/impl' -import { UserInterface } from '@isomera/interfaces' - -export const UserSecurityView: React.FC = () => { - const { generate2FA, verify2FA, turnOff2FA, isLoading } = - useTwoFactorAuthHook() - const [qrCodeImage, setQrCodeImage] = useState(null) - const [code, setCode] = useState('') - const [verificationError, setVerificationError] = useState( - null - ) - const { - user, - recoveryCodes, - updateRecoveryCodes, - recoveryViewed, - updateRecoveryViewed, - setUser - } = useSession() - - const [showRecoveryCodes, setShowRecoveryCodes] = useState(false) - - useEffect(() => { - let hideTimer: NodeJS.Timeout | null = null - if (recoveryCodes && !recoveryViewed && !showRecoveryCodes) { - setShowRecoveryCodes(true) - hideTimer = setTimeout(() => { - setShowRecoveryCodes(false) - updateRecoveryViewed() - }, 60000) - } - - return () => { - if (hideTimer) { - clearTimeout(hideTimer) - } - } - }, [recoveryCodes, recoveryViewed, showRecoveryCodes, updateRecoveryViewed]) - - const [isTwoFAEnabled, setIsTwoFAEnabled] = useState( - user?.isTwoFAEnabled ?? false - ) - - const handleToggle2FA = async (): Promise => { - if (!isTwoFAEnabled) { - try { - const response = await generate2FA() - if (response.status === 'ok') { - setQrCodeImage(response.image) - } else { - console.error('Failed to turn on 2FA:', response) - } - } catch (error) { - console.error('Error turning on 2FA:', error) - } - } - } - - const handle2FAToggleChange = ( - event: React.ChangeEvent - ) => { - setIsTwoFAEnabled(event.target.checked) - if (!isTwoFAEnabled) { - handleToggle2FA() - } - } - - const handleSubmitCode = async (e: React.FormEvent) => { - e.preventDefault() - try { - const codeNoSpace = code.split(' ').join('') - const response = await verify2FA({ code: codeNoSpace }) - if (response.status === 'ok' && response.secret) { - updateRecoveryCodes([response.secret]) - setQrCodeImage(null) - setCode('') - } else { - setVerificationError('Failed to verify 2FA code.') - } - } catch (error) { - console.error('Error verifying 2FA code:', error) - setVerificationError('An error occurred while verifying the 2FA code.') - } - } - - const handleTurnoff2FA = async (e: React.FormEvent) => { - e.preventDefault() - try { - const codeNoSpace = code.split(' ').join('') - const { status, access_token, refresh_token } = await turnOff2FA({ - code: codeNoSpace - }) - if (status === 'ok') { - setCode('') - setAuthState({ accessToken: access_token, refreshToken: refresh_token }) - const newUser = { ...user } - newUser.isTwoFAEnabled = false - newUser.accessToken = access_token - newUser.refreshToken = refresh_token - setUser(newUser as UserInterface) - } else { - setVerificationError('Failed to verify 2FA code.') - } - } catch (error) { - console.error('Error verifying 2FA code:', error) - setVerificationError('An error occurred while verifying the 2FA code.') - } - } - - useEffect(() => { - if (user) { - setIsTwoFAEnabled(user.isTwoFAEnabled) - } - }, [user, user?.isTwoFAEnabled]) - - return ( -
-

Security page

- {!qrCodeImage && ( - - )} - {user?.isTwoFAEnabled && !isTwoFAEnabled && ( -
-
- -
- - {verificationError && ( -

{verificationError}

- )} -
- )} - {qrCodeImage && ( - <> -

- Open your Authenticator app, for example, Google Authenticator, DUO, - Microsoft Security app, and scan the QR code: -

- 2FA QR Code -
-
- -
- - {verificationError && ( -

{verificationError}

- )} -
- - )} - {!recoveryViewed && recoveryCodes && ( -
-

Recovery Codes

-

- Keep these codes in a safe place. You can use them to recover access - to your account if you lose your 2FA device. -
- These will be available for one minute only. -

-
    - {recoveryCodes.map((code, index) => ( -
  • {code}
  • - ))} -
-
- -
-
- )} -
- ) -} +import React, { useEffect, useState } from 'react' +import useSession from '../../hooks/useSession' +import { useTwoFactorAuthHook } from '../../hooks/use2FA' +import { setAuthState } from '@isomera/impl' +import { UserInterface } from '@isomera/interfaces' + +export const UserSecurityView: React.FC = () => { + const { generate2FA, verify2FA, turnOff2FA, isLoading } = + useTwoFactorAuthHook() + const [qrCodeImage, setQrCodeImage] = useState(null) + const [code, setCode] = useState('') + const [verificationError, setVerificationError] = useState( + null + ) + const { + user, + recoveryCodes, + updateRecoveryCodes, + recoveryViewed, + updateRecoveryViewed, + setUser + } = useSession() + + const [showRecoveryCodes, setShowRecoveryCodes] = useState(false) + + useEffect(() => { + let hideTimer: NodeJS.Timeout | null = null + if (recoveryCodes && !recoveryViewed && !showRecoveryCodes) { + setShowRecoveryCodes(true) + hideTimer = setTimeout(() => { + setShowRecoveryCodes(false) + updateRecoveryViewed() + }, 60000) + } + + return () => { + if (hideTimer) { + clearTimeout(hideTimer) + } + } + }, [recoveryCodes, recoveryViewed, showRecoveryCodes, updateRecoveryViewed]) + + const [isTwoFAEnabled, setIsTwoFAEnabled] = useState( + user?.isTwoFAEnabled ?? false + ) + + const handleToggle2FA = async (): Promise => { + if (!isTwoFAEnabled) { + try { + const response = await generate2FA() + if (response.status === 'ok') { + setQrCodeImage(response.image) + } else { + console.error('Failed to turn on 2FA:', response) + } + } catch (error) { + console.error('Error turning on 2FA:', error) + } + } + } + + const handle2FAToggleChange = ( + event: React.ChangeEvent + ) => { + setIsTwoFAEnabled(event.target.checked) + if (!isTwoFAEnabled) { + handleToggle2FA() + } + } + + const handleSubmitCode = async (e: React.FormEvent) => { + e.preventDefault() + try { + const codeNoSpace = code.split(' ').join('') + const response = await verify2FA({ code: codeNoSpace }) + if (response.status === 'ok' && response.secret) { + updateRecoveryCodes([response.secret]) + setQrCodeImage(null) + setCode('') + } else { + setVerificationError('Failed to verify 2FA code.') + } + } catch (error) { + console.error('Error verifying 2FA code:', error) + setVerificationError('An error occurred while verifying the 2FA code.') + } + } + + const handleTurnoff2FA = async (e: React.FormEvent) => { + e.preventDefault() + try { + const codeNoSpace = code.split(' ').join('') + const { status, access_token, refresh_token } = await turnOff2FA({ + code: codeNoSpace + }) + if (status === 'ok') { + setCode('') + setAuthState({ accessToken: access_token, refreshToken: refresh_token }) + const newUser = { ...user } + newUser.isTwoFAEnabled = false + newUser.accessToken = access_token + newUser.refreshToken = refresh_token + setUser(newUser as UserInterface) + } else { + setVerificationError('Failed to verify 2FA code.') + } + } catch (error) { + console.error('Error verifying 2FA code:', error) + setVerificationError('An error occurred while verifying the 2FA code.') + } + } + + useEffect(() => { + if (user) { + setIsTwoFAEnabled(user.isTwoFAEnabled) + } + }, [user, user?.isTwoFAEnabled]) + + return ( +
+

Security page

+ {!qrCodeImage && ( + + )} + {user?.isTwoFAEnabled && !isTwoFAEnabled && ( +
+
+ +
+ + {verificationError && ( +

{verificationError}

+ )} +
+ )} + {qrCodeImage && ( + <> +

+ Open your Authenticator app, for example, Google Authenticator, DUO, + Microsoft Security app, and scan the QR code: +

+ 2FA QR Code +
+
+ +
+ + {verificationError && ( +

{verificationError}

+ )} +
+ + )} + {!recoveryViewed && recoveryCodes && ( +
+

Recovery Codes

+

+ Keep these codes in a safe place. You can use them to recover access + to your account if you lose your 2FA device. +
+ These will be available for one minute only. +

+
    + {recoveryCodes.map((code, index) => ( +
  • {code}
  • + ))} +
+
+ +
+
+ )} +
+ ) +}