11'use client' ;
22
3- import { sendVerificationEmail , triggerInitialMFA , verifyMFA } from '@/lib/auth' ;
3+ import {
4+ reauthenticateUser ,
5+ sendVerificationEmail ,
6+ triggerInitialMFA ,
7+ verifyMFA ,
8+ } from '@/lib/auth' ;
49import { auth } from '@/lib/firebase' ;
510import { useTypedSelector } from '@/lib/hooks/store' ;
6- import { Box , Button , TextField , Typography } from '@mui/material' ;
11+ import { Alert , Box , Button , TextField , Typography } from '@mui/material' ;
712import { useRollbar } from '@rollbar/react' ;
813import { useTranslations } from 'next-intl' ;
914import { useRouter } from 'next/navigation' ;
10- import { useState } from 'react' ;
15+ import { useEffect , useRef , useState } from 'react' ;
1116import PhoneInput from '../forms/PhoneInput' ;
1217
1318const buttonStyle = {
@@ -27,24 +32,89 @@ const SetupMFA = () => {
2732 const [ verificationId , setVerificationId ] = useState ( '' ) ;
2833 const [ verificationCode , setVerificationCode ] = useState ( '' ) ;
2934 const [ error , setError ] = useState ( '' ) ;
35+ const [ showReauth , setShowReauth ] = useState ( false ) ;
36+ const [ password , setPassword ] = useState ( '' ) ;
37+ const [ isReauthenticating , setIsReauthenticating ] = useState ( false ) ;
38+ const recaptchaContainerRef = useRef < HTMLDivElement > ( null ) ;
39+ const recaptchaCleanupRef = useRef < ( ( ) => void ) | null > ( null ) ;
3040
41+ // Clean up reCAPTCHA on unmount
42+ useEffect ( ( ) => {
43+ return ( ) => {
44+ if ( recaptchaContainerRef . current ) {
45+ recaptchaContainerRef . current . innerHTML = '' ;
46+ }
47+ if ( recaptchaCleanupRef . current ) {
48+ recaptchaCleanupRef . current ( ) ;
49+ recaptchaCleanupRef . current = null ;
50+ }
51+ } ;
52+ } , [ ] ) ;
53+
54+ const handleReauthentication = async ( ) => {
55+ if ( ! password . trim ( ) ) {
56+ setError ( t ( 'form.passwordRequired' ) ) ;
57+ return ;
58+ }
59+
60+ setIsReauthenticating ( true ) ;
61+ setError ( '' ) ;
62+
63+ try {
64+ await reauthenticateUser ( password ) ;
65+ setShowReauth ( false ) ;
66+ setPassword ( '' ) ;
67+ // Reset the MFA setup process
68+ setVerificationId ( '' ) ;
69+ setVerificationCode ( '' ) ;
70+ setPhoneNumber ( '' ) ;
71+ } catch ( error : any ) {
72+ rollbar . error ( 'Reauthentication error:' , error ) ;
73+ if ( error . code === 'auth/wrong-password' ) {
74+ setError ( t ( 'form.firebase.wrongPassword' ) ) ;
75+ } else if ( error . code === 'auth/too-many-requests' ) {
76+ setError ( t ( 'form.firebase.tooManyAttempts' ) ) ;
77+ } else {
78+ setError ( t ( 'form.reauthenticationError' ) ) ;
79+ }
80+ } finally {
81+ setIsReauthenticating ( false ) ;
82+ }
83+ } ;
3184 const handleEnrollMFA = async ( ) => {
3285 if ( ! userVerifiedEmail ) {
3386 setError ( t ( 'form.emailNotVerified' ) ) ;
3487 rollbar . error ( 'MFA setup page reached before email is verified' ) ;
3588 return ;
3689 }
3790
91+ setError ( '' ) ;
92+
93+ // Clean up any existing reCAPTCHA before creating a new one
94+ if ( recaptchaContainerRef . current ) {
95+ recaptchaContainerRef . current . innerHTML = '' ;
96+ }
97+ if ( recaptchaCleanupRef . current ) {
98+ recaptchaCleanupRef . current ( ) ;
99+ recaptchaCleanupRef . current = null ;
100+ }
101+
38102 const { verificationId, error } = await triggerInitialMFA ( phoneNumber ) ;
39103 if ( error ) {
40- setError ( t ( 'form.mfaEnrollError' ) ) ;
41- rollbar . error ( 'MFA enrollment trigger error:' , error ) ;
104+ if ( error . code === 'auth/requires-recent-login' ) {
105+ setShowReauth ( true ) ;
106+ setError ( '' ) ;
107+ } else {
108+ setError ( t ( 'form.mfaEnrollError' ) ) ;
109+ rollbar . error ( 'MFA enrollment trigger error:' , error ) ;
110+ }
42111 } else {
43112 setVerificationId ( verificationId ! ) ;
44113 }
45114 } ;
46115
47116 const handleFinalizeMFA = async ( ) => {
117+ setError ( '' ) ;
48118 const { success, error } = await verifyMFA ( verificationId , verificationCode ) ;
49119 if ( success ) {
50120 router . push ( '/admin/dashboard' ) ;
@@ -55,18 +125,69 @@ const SetupMFA = () => {
55125 } ;
56126
57127 const handleSendVerificationEmail = async ( ) => {
128+ setError ( '' ) ;
58129 const user = auth . currentUser ;
59130 if ( user ) {
60131 const { error } = await sendVerificationEmail ( user ) ;
61132 if ( error ) {
133+ rollbar . error ( 'Send verification email error:' , error ) ;
62134 setError ( t ( 'form.emailVerificationError' ) ) ;
63135 } else {
64- rollbar . error ( 'Send verification email error:' , error || ' Undefined' ) ;
65- setError ( t ( 'form.emailVerificationSent' ) ) ;
136+ // Show success message instead of error
137+ setError ( '' ) ;
138+ // You might want to show a success state here instead
66139 }
67140 }
68141 } ;
69142
143+ if ( showReauth ) {
144+ return (
145+ < Box >
146+ < Typography variant = "h3" > { t ( 'setupMFA.reauthTitle' ) } </ Typography >
147+ < Typography mb = { 2 } > { t ( 'setupMFA.reauthDescription' ) } </ Typography >
148+
149+ < TextField
150+ id = "password"
151+ type = "password"
152+ value = { password }
153+ onChange = { ( e ) => setPassword ( e . target . value ) }
154+ label = { t ( 'form.passwordLabel' ) }
155+ fullWidth
156+ variant = "standard"
157+ margin = "normal"
158+ required
159+ />
160+
161+ { error && (
162+ < Alert severity = "error" sx = { { mt : 2 } } >
163+ { error }
164+ </ Alert >
165+ ) }
166+
167+ < Box sx = { { mt : 2 , display : 'flex' , gap : 2 } } >
168+ < Button
169+ variant = "outlined"
170+ onClick = { ( ) => {
171+ setShowReauth ( false ) ;
172+ setPassword ( '' ) ;
173+ setError ( '' ) ;
174+ } }
175+ disabled = { isReauthenticating }
176+ >
177+ { t ( 'setupMFA.cancelReauth' ) }
178+ </ Button >
179+ < Button
180+ variant = "contained"
181+ color = "secondary"
182+ onClick = { handleReauthentication }
183+ disabled = { isReauthenticating || ! password . trim ( ) }
184+ >
185+ { isReauthenticating ? t ( 'setupMFA.reauthenticating' ) : t ( 'setupMFA.confirmReauth' ) }
186+ </ Button >
187+ </ Box >
188+ </ Box >
189+ ) ;
190+ }
70191 return (
71192 < Box >
72193 < Typography variant = "h3" > { t ( 'setupMFA.title' ) } </ Typography >
@@ -93,6 +214,7 @@ const SetupMFA = () => {
93214 < >
94215 < Typography > { t ( 'setupMFA.enterCodeHelperText' ) } </ Typography >
95216 < TextField
217+ id = "verificationCode"
96218 value = { verificationCode }
97219 onChange = { ( e ) => setVerificationCode ( e . target . value ) }
98220 label = { t ( 'form.verificationCodeLabel' ) }
@@ -111,11 +233,11 @@ const SetupMFA = () => {
111233 </ >
112234 ) }
113235 { error && (
114- < Typography color = "error" sx = { { mt : '1rem !important' } } >
236+ < Alert severity = "error" sx = { { mt : 2 } } >
115237 { error }
116- </ Typography >
238+ </ Alert >
117239 ) }
118- < div id = "recaptcha-container" > </ div >
240+ < div id = "recaptcha-container" ref = { recaptchaContainerRef } > </ div >
119241 </ Box >
120242 ) ;
121243} ;
0 commit comments