Skip to content

Commit

Permalink
Fix recovery codes display error and double toast message
Browse files Browse the repository at this point in the history
  • Loading branch information
PhamAnhHoang committed Feb 17, 2024
1 parent 6d6474d commit 22a8725
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 24 deletions.
4 changes: 4 additions & 0 deletions apps/platform/src/contexts/authContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export type AuthContextData = {
loadingUserData: boolean
setUser: (user: UserInterface | undefined) => void
loginWith2FA: (accessToken: string, refreshToken: string) => void
updateRecoveryCodes: (codes?: string[]) => void
recoveryCodes: string[] | null
recoveryViewed: boolean
updateRecoveryViewed: () => void
}

const AuthContext = createContext({} as AuthContextData)
Expand Down
10 changes: 6 additions & 4 deletions apps/platform/src/hooks/verify2FAHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ const validationSchema = Yup.object({
code: Yup.string().required('2FA code is required')
})

interface Options {
interface UseVerify2FAHookOptions {
onSuccess?: (response: any) => void

Check warning on line 15 in apps/platform/src/hooks/verify2FAHook.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type
onError?: (error: any) => void
}

export const useVerify2FAHook = (options: Options) => {
export const useVerify2FAHook = (options: UseVerify2FAHookOptions) => {
const { authenticate, isLoading } = useTwoFactorAuthHook()

const { onSuccess, onError } = options

const onSubmit = async (values: typeof initialValues) => {
try {
const response = await authenticate({
code: values.code.split(' ').join('')
})
options.onSuccess && options.onSuccess(response)
onSuccess && onSuccess(response)
} catch (error) {
options.onError && options.onError(error)
onError && onError(error)
}
}

Expand Down
3 changes: 2 additions & 1 deletion apps/platform/src/layouts/private.layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ interface Props {
}

export const PrivateLayout: FC<Props> = ({ children }) => {
const { user, setUser } = useSession()
const { user, setUser, updateRecoveryCodes } = useSession()
const navigate = useNavigate()

const onSuccess = (message: string) => {
toast.success(message)
setUser(undefined)
clearAuthState()
updateRecoveryCodes()
navigate(pages.login.path)
}

Expand Down
20 changes: 19 additions & 1 deletion apps/platform/src/providers/authProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ type Props = {
function AuthProvider({ children }: Props) {
const [user, setUser] = useState<UserInterface | undefined>()
const [loadingUserData, setLoadingUserData] = useState(true)
const [recoveryCodes, setRecoveryCodes] = useState<string[] | null>(null)
const [recoveryViewed, setRecoveryViewed] = useState<boolean>(false)

const updateRecoveryCodes = (codes?: string[]) => {
if (codes) {
setRecoveryCodes(codes)
} else {
setRecoveryCodes(null)
}
}

const updateRecoveryViewed = () => {
setRecoveryViewed(true)
}

const fetchUser = useCallback(async () => {
const token = getAccessToken()
Expand Down Expand Up @@ -54,7 +68,11 @@ function AuthProvider({ children }: Props) {
user,
loadingUserData,
setUser,
loginWith2FA
loginWith2FA,
updateRecoveryCodes,
recoveryCodes,
recoveryViewed,
updateRecoveryViewed
}}
>
{children}
Expand Down
28 changes: 18 additions & 10 deletions apps/platform/src/views/auth/disable-2fa.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,42 @@ export const Disable2FAView = () => {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const [message, setMessage] = useState('')
const [disabled, setDisabled] = useState<boolean>(false)

const { disable2FA, isLoading } = useTwoFactorAuthHook()
const email = searchParams.get('email')
const code = searchParams.get('code')
const msg = 'Your 2fa was disabled, please log-in and enable 2fa again.'

useEffect(() => {
const email = searchParams.get('email')
const code = searchParams.get('code')
const timeoutId: NodeJS.Timeout | null = null

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

return () => {
if (timeoutId) {
clearTimeout(timeoutId)
}
}
}, [])

return (
<div>
{isLoading ? <p>{message}</p> : <p>Disabling 2FA, please wait...</p>}
{isLoading ? <p>Disabling 2FA, please wait...</p> : <p>{message}</p>}
</div>
)
}
49 changes: 41 additions & 8 deletions apps/platform/src/views/user /profile-security.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,32 @@ export const UserSecurityView: React.FC = () => {
const [verificationError, setVerificationError] = useState<string | null>(
null
)
const [recoveryCodes, setRecoveryCodes] = useState<string[] | null>(null)
const { user } = useSession()
const {
user,
recoveryCodes,
updateRecoveryCodes,
recoveryViewed,
updateRecoveryViewed
} = 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<boolean>(
user?.isTwoFAEnabled ?? false
Expand All @@ -34,8 +58,10 @@ export const UserSecurityView: React.FC = () => {
const handle2FAToggleChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setIsTwoFAEnabled(event.target.checked)
handleToggle2FA()
if (!isTwoFAEnabled) {
setIsTwoFAEnabled(event.target.checked)
handleToggle2FA()
}
}

const handleSubmitCode = async (e: React.FormEvent) => {
Expand All @@ -44,7 +70,7 @@ export const UserSecurityView: React.FC = () => {
const codeNoSpace = code.split(' ').join('')
const response = await verify2FA({ code: codeNoSpace })
if (response.status === 'ok' && response.secret) {
setRecoveryCodes([response.secret])
updateRecoveryCodes([response.secret])
setQrCodeImage(null)
setCode('')
} else {
Expand All @@ -71,7 +97,7 @@ export const UserSecurityView: React.FC = () => {
type="checkbox"
checked={isTwoFAEnabled}
onChange={handle2FAToggleChange}
disabled={isLoading}
disabled={isLoading || isTwoFAEnabled}
/>
{isTwoFAEnabled ? '2FA Enabled' : 'Enable 2FA'}
</label>
Expand Down Expand Up @@ -103,18 +129,25 @@ export const UserSecurityView: React.FC = () => {
</form>
</>
)}
{recoveryCodes && (
{!recoveryViewed && recoveryCodes && (
<div>
<h2>Recovery Codes</h2>
<p>
<p className=" text-red-500">
Keep these codes in a safe place. You can use them to recover access
to your account if you lose your 2FA device.
<br />
These will be available for one minute only.
</p>
<ul>
{recoveryCodes.map((code, index) => (
<li key={index}>{code}</li>
))}
</ul>
<div>
<button onClick={updateRecoveryViewed}>
I have saved my recovery codes
</button>
</div>
</div>
)}
</div>
Expand Down

0 comments on commit 22a8725

Please sign in to comment.