@@ -2,21 +2,22 @@ import React, { useEffect, useState } from 'react';
22import Modal , { RequiredModalProps } from '@/components/elements/Modal' ;
33import { Form , Formik , FormikHelpers } from 'formik' ;
44import { object , string } from 'yup' ;
5- import Field from '@/components/elements/Field' ;
65import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl' ;
76import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor' ;
8- import FlashMessageRender from '@/components/FlashMessageRender' ;
97import { Actions , useStoreActions } from 'easy-peasy' ;
108import { ApplicationStore } from '@/state' ;
119import { httpErrorToHuman } from '@/api/http' ;
10+ import FlashMessageRender from '@/components/FlashMessageRender' ;
11+ import Field from '@/components/elements/Field' ;
1212
1313interface Values {
1414 code : string ;
1515}
1616
17- export default ( { ...props } : RequiredModalProps ) => {
17+ export default ( { onDismissed , ...props } : RequiredModalProps ) => {
1818 const [ token , setToken ] = useState ( '' ) ;
1919 const [ loading , setLoading ] = useState ( true ) ;
20+ const [ recoveryTokens , setRecoveryTokens ] = useState < string [ ] > ( [ ] ) ;
2021
2122 const updateUserData = useStoreActions ( ( actions : Actions < ApplicationStore > ) => actions . user . updateUserData ) ;
2223 const { addError, clearFlashes } = useStoreActions ( ( actions : Actions < ApplicationStore > ) => actions . flashes ) ;
@@ -27,22 +28,30 @@ export default ({ ...props }: RequiredModalProps) => {
2728 . then ( setToken )
2829 . catch ( error => {
2930 console . error ( error ) ;
31+ addError ( { message : httpErrorToHuman ( error ) , key : 'account:two-factor' } ) ;
3032 } ) ;
3133 } , [ ] ) ;
3234
3335 const submit = ( { code } : Values , { setSubmitting } : FormikHelpers < Values > ) => {
3436 clearFlashes ( 'account:two-factor' ) ;
3537 enableAccountTwoFactor ( code )
36- . then ( ( ) => {
37- updateUserData ( { useTotp : true } ) ;
38- props . onDismissed ( ) ;
38+ . then ( tokens => {
39+ setRecoveryTokens ( tokens ) ;
3940 } )
4041 . catch ( error => {
4142 console . error ( error ) ;
4243
4344 addError ( { message : httpErrorToHuman ( error ) , key : 'account:two-factor' } ) ;
44- setSubmitting ( false ) ;
45- } ) ;
45+ } )
46+ . then ( ( ) => setSubmitting ( false ) ) ;
47+ } ;
48+
49+ const dismiss = ( ) => {
50+ if ( recoveryTokens . length > 0 ) {
51+ updateUserData ( { useTotp : true } ) ;
52+ }
53+
54+ onDismissed ( ) ;
4655 } ;
4756
4857 return (
@@ -58,47 +67,73 @@ export default ({ ...props }: RequiredModalProps) => {
5867 { ( { isSubmitting, isValid } ) => (
5968 < Modal
6069 { ...props }
70+ onDismissed = { dismiss }
6171 dismissable = { ! isSubmitting }
6272 showSpinnerOverlay = { loading || isSubmitting }
73+ closeOnEscape = { ! recoveryTokens }
74+ closeOnBackground = { ! recoveryTokens }
6375 >
64- < Form className = { 'mb-0' } >
65- < FlashMessageRender className = { 'mb-6' } byKey = { 'account:two-factor' } />
66- < div className = { 'flex flex-wrap' } >
67- < div className = { 'w-full md:flex-1' } >
68- < div className = { 'w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto' } >
69- { ! token || ! token . length ?
70- < img
71- src = { 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' }
72- className = { 'w-64 h-64 rounded' }
73- />
74- :
75- < img
76- src = { `https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${ token } ` }
77- onLoad = { ( ) => setLoading ( false ) }
78- className = { 'w-full h-full shadow-none rounded-0' }
79- />
80- }
81- </ div >
76+ { recoveryTokens . length > 0 ?
77+ < >
78+ < h2 className = { 'mb-4' } > Two-factor authentication enabled</ h2 >
79+ < p className = { 'text-neutral-300' } >
80+ Two-factor authentication has been enabled on your account. Should you loose access to
81+ this device you'll need to use on of the codes displayed below in order to access your
82+ account.
83+ </ p >
84+ < p className = { 'text-neutral-300 mt-4' } >
85+ < strong > These codes will not be displayed again.</ strong > Please take note of them now
86+ by storing them in a secure repository such as a password manager.
87+ </ p >
88+ < pre className = { 'mt-4 rounded font-mono bg-neutral-900 p-4' } >
89+ { recoveryTokens . map ( token => < code key = { token } className = { 'block mb-1' } > { token } </ code > ) }
90+ </ pre >
91+ < div className = { 'text-right' } >
92+ < button className = { 'mt-6 btn btn-lg btn-primary' } onClick = { dismiss } >
93+ Close
94+ </ button >
8295 </ div >
83- < div className = { 'w-full mt-6 md:mt-0 md:flex-1 md:flex md:flex-col' } >
84- < div className = { 'flex-1' } >
85- < Field
86- id = { 'code' }
87- name = { 'code' }
88- type = { 'text' }
89- title = { 'Code From Authenticator' }
90- description = { 'Enter the code from your authenticator device after scanning the QR image.' }
91- autoFocus = { ! loading }
92- />
96+ </ >
97+ :
98+ < Form className = { 'mb-0' } >
99+ < FlashMessageRender className = { 'mb-6' } byKey = { 'account:two-factor' } />
100+ < div className = { 'flex flex-wrap' } >
101+ < div className = { 'w-full md:flex-1' } >
102+ < div className = { 'w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto' } >
103+ { ! token || ! token . length ?
104+ < img
105+ src = { 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' }
106+ className = { 'w-64 h-64 rounded' }
107+ />
108+ :
109+ < img
110+ src = { `https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${ token } ` }
111+ onLoad = { ( ) => setLoading ( false ) }
112+ className = { 'w-full h-full shadow-none rounded-0' }
113+ />
114+ }
115+ </ div >
93116 </ div >
94- < div className = { 'mt-6 md:mt-0 text-right' } >
95- < button className = { 'btn btn-primary btn-sm' } disabled = { ! isValid } >
96- Setup
97- </ button >
117+ < div className = { 'w-full mt-6 md:mt-0 md:flex-1 md:flex md:flex-col' } >
118+ < div className = { 'flex-1' } >
119+ < Field
120+ id = { 'code' }
121+ name = { 'code' }
122+ type = { 'text' }
123+ title = { 'Code From Authenticator' }
124+ description = { 'Enter the code from your authenticator device after scanning the QR image.' }
125+ autoFocus = { ! loading }
126+ />
127+ </ div >
128+ < div className = { 'mt-6 md:mt-0 text-right' } >
129+ < button className = { 'btn btn-primary btn-sm' } disabled = { ! isValid } >
130+ Setup
131+ </ button >
132+ </ div >
98133 </ div >
99134 </ div >
100- </ div >
101- </ Form >
135+ </ Form >
136+ }
102137 </ Modal >
103138 ) }
104139 </ Formik >
0 commit comments