2023-05-05 21:23:59 +08:00
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2023-07-07 12:30:07 +08:00
import React from "react" ;
2024-02-06 20:17:59 +08:00
import { Button , Col , Result , Row , Spin , Steps } from "antd" ;
2023-07-07 12:30:07 +08:00
import { withRouter } from "react-router-dom" ;
2023-06-21 18:56:37 +08:00
import * as ApplicationBackend from "../backend/ApplicationBackend" ;
2023-05-05 21:23:59 +08:00
import * as Setting from "../Setting" ;
import i18next from "i18next" ;
import * as MfaBackend from "../backend/MfaBackend" ;
2023-07-07 12:30:07 +08:00
import { CheckOutlined , KeyOutlined , UserOutlined } from "@ant-design/icons" ;
import CheckPasswordForm from "./mfa/CheckPasswordForm" ;
import MfaEnableForm from "./mfa/MfaEnableForm" ;
import { MfaVerifyForm } from "./mfa/MfaVerifyForm" ;
2023-05-05 21:23:59 +08:00
2023-06-21 18:56:37 +08:00
export const EmailMfaType = "email" ;
2023-05-05 21:23:59 +08:00
export const SmsMfaType = "sms" ;
export const TotpMfaType = "app" ;
2023-06-24 18:39:54 +08:00
export const RecoveryMfaType = "recovery" ;
2023-05-05 21:23:59 +08:00
class MfaSetupPage extends React . Component {
constructor ( props ) {
super ( props ) ;
2023-07-07 12:30:07 +08:00
const params = new URLSearchParams ( props . location . search ) ;
const { location } = this . props ;
2023-05-05 21:23:59 +08:00
this . state = {
account : props . account ,
2023-07-07 12:30:07 +08:00
application : null ,
2024-11-04 00:04:47 +08:00
applicationName : props . account . signupApplication ? ? localStorage . getItem ( "applicationName" ) ? ? "" ,
2023-07-07 12:30:07 +08:00
current : location . state ? . from !== undefined ? 1 : 0 ,
2023-05-05 21:23:59 +08:00
mfaProps : null ,
2023-07-07 12:30:07 +08:00
mfaType : params . get ( "mfaType" ) ? ? SmsMfaType ,
isPromptPage : props . isPromptPage || location . state ? . from !== undefined ,
2024-02-06 20:17:59 +08:00
loading : false ,
2023-05-05 21:23:59 +08:00
} ;
}
componentDidMount ( ) {
this . getApplication ( ) ;
2023-07-07 12:30:07 +08:00
if ( this . state . current === 1 ) {
2024-02-06 20:17:59 +08:00
this . setState ( {
loading : true ,
} ) ;
setTimeout ( ( ) => {
this . initMfaProps ( ) ;
} , 200 ) ;
2023-07-07 12:30:07 +08:00
}
2023-05-05 21:23:59 +08:00
}
2023-05-17 01:13:13 +08:00
componentDidUpdate ( prevProps , prevState , snapshot ) {
2023-07-07 12:30:07 +08:00
if ( this . state . mfaType !== prevState . mfaType || this . state . current !== prevState . current ) {
if ( this . state . current === 1 ) {
this . initMfaProps ( ) ;
}
2023-05-17 01:13:13 +08:00
}
}
2023-05-05 21:23:59 +08:00
getApplication ( ) {
2023-05-17 01:13:13 +08:00
ApplicationBackend . getApplication ( "admin" , this . state . applicationName )
2023-06-27 20:33:47 +07:00
. then ( ( res ) => {
if ( res !== null ) {
if ( res . status === "error" ) {
Setting . showMessage ( "error" , res . msg ) ;
return ;
}
2023-05-05 21:23:59 +08:00
this . setState ( {
2023-07-23 09:49:16 +08:00
application : res . data ,
2023-05-05 21:23:59 +08:00
} ) ;
} else {
Setting . showMessage ( "error" , i18next . t ( "mfa:Failed to get application" ) ) ;
}
} ) ;
}
2023-07-07 12:30:07 +08:00
initMfaProps ( ) {
MfaBackend . MfaSetupInitiate ( {
mfaType : this . state . mfaType ,
... this . getUser ( ) ,
} ) . then ( ( res ) => {
if ( res . status === "ok" ) {
this . setState ( {
mfaProps : res . data ,
2024-02-06 20:17:59 +08:00
loading : false ,
2023-07-07 12:30:07 +08:00
} ) ;
} else {
Setting . showMessage ( "error" , i18next . t ( "mfa:Failed to initiate MFA" ) ) ;
}
} ) ;
}
2023-05-05 21:23:59 +08:00
getUser ( ) {
2023-07-07 12:30:07 +08:00
return this . props . account ;
}
renderMfaTypeSwitch ( ) {
const renderSmsLink = ( ) => {
2024-01-28 16:46:35 +08:00
if ( this . state . mfaType === SmsMfaType ) {
2023-07-07 12:30:07 +08:00
return null ;
}
return ( < Button type = { "link" } onClick = { ( ) => {
this . setState ( {
mfaType : SmsMfaType ,
} ) ;
this . props . history . push ( ` /mfa/setup?mfaType= ${ SmsMfaType } ` ) ;
}
} > { i18next . t ( "mfa:Use SMS" ) } < / B u t t o n >
) ;
2023-05-05 21:23:59 +08:00
} ;
2023-07-07 12:30:07 +08:00
const renderEmailLink = ( ) => {
2024-01-28 16:46:35 +08:00
if ( this . state . mfaType === EmailMfaType ) {
2023-07-07 12:30:07 +08:00
return null ;
}
return ( < Button type = { "link" } onClick = { ( ) => {
this . setState ( {
mfaType : EmailMfaType ,
} ) ;
this . props . history . push ( ` /mfa/setup?mfaType= ${ EmailMfaType } ` ) ;
}
} > { i18next . t ( "mfa:Use Email" ) } < / B u t t o n >
) ;
} ;
const renderTotpLink = ( ) => {
2024-01-28 16:46:35 +08:00
if ( this . state . mfaType === TotpMfaType ) {
2023-07-07 12:30:07 +08:00
return null ;
}
return ( < Button type = { "link" } onClick = { ( ) => {
this . setState ( {
mfaType : TotpMfaType ,
} ) ;
this . props . history . push ( ` /mfa/setup?mfaType= ${ TotpMfaType } ` ) ;
}
} > { i18next . t ( "mfa:Use Authenticator App" ) } < / B u t t o n >
) ;
} ;
return ! this . state . isPromptPage ? (
< React . Fragment >
{ renderSmsLink ( ) }
{ renderEmailLink ( ) }
{ renderTotpLink ( ) }
< / R e a c t . F r a g m e n t >
) : null ;
2023-05-05 21:23:59 +08:00
}
renderStep ( ) {
switch ( this . state . current ) {
case 0 :
2023-06-24 18:39:54 +08:00
return (
< CheckPasswordForm
user = { this . getUser ( ) }
onSuccess = { ( ) => {
this . setState ( {
current : this . state . current + 1 ,
} ) ;
} }
onFail = { ( res ) => {
2023-07-07 12:30:07 +08:00
Setting . showMessage ( "error" , i18next . t ( "mfa:Failed to initiate MFA" ) + ": " + res . msg ) ;
2023-06-24 18:39:54 +08:00
} }
/ >
) ;
2023-05-05 21:23:59 +08:00
case 1 :
2023-06-21 18:56:37 +08:00
return (
< div >
< MfaVerifyForm
2023-06-24 18:39:54 +08:00
mfaProps = { this . state . mfaProps }
2023-06-21 18:56:37 +08:00
application = { this . state . application }
user = { this . props . account }
onSuccess = { ( ) => {
this . setState ( {
current : this . state . current + 1 ,
} ) ;
} }
onFail = { ( res ) => {
2023-07-07 12:30:07 +08:00
Setting . showMessage ( "error" , i18next . t ( "general:Failed to verify" ) + ": " + res . msg ) ;
2023-06-21 18:56:37 +08:00
} }
/ >
< Col span = { 24 } style = { { display : "flex" , justifyContent : "left" } } >
2023-07-07 12:30:07 +08:00
{ this . renderMfaTypeSwitch ( ) }
2023-06-21 18:56:37 +08:00
< / C o l >
< / d i v >
) ;
2023-05-05 21:23:59 +08:00
case 2 :
2023-06-24 18:39:54 +08:00
return (
2023-07-07 12:30:07 +08:00
< MfaEnableForm user = { this . getUser ( ) } mfaType = { this . state . mfaType } recoveryCodes = { this . state . mfaProps . recoveryCodes }
2023-06-24 18:39:54 +08:00
onSuccess = { ( ) => {
Setting . showMessage ( "success" , i18next . t ( "general:Enabled successfully" ) ) ;
2023-07-08 22:35:31 +08:00
this . props . onfinish ( ) ;
2024-01-28 16:46:35 +08:00
const mfaRedirectUrl = localStorage . getItem ( "mfaRedirectUrl" ) ;
if ( mfaRedirectUrl !== undefined && mfaRedirectUrl !== null ) {
2023-07-07 12:30:07 +08:00
Setting . goToLink ( localStorage . getItem ( "mfaRedirectUrl" ) ) ;
localStorage . removeItem ( "mfaRedirectUrl" ) ;
2023-06-24 18:39:54 +08:00
} else {
2023-07-07 12:30:07 +08:00
this . props . history . push ( "/account" ) ;
2023-06-24 18:39:54 +08:00
}
} }
onFail = { ( res ) => {
Setting . showMessage ( "error" , ` ${ i18next . t ( "general:Failed to enable" ) } : ${ res . msg } ` ) ;
} } / >
) ;
2023-05-05 21:23:59 +08:00
default :
return null ;
}
}
render ( ) {
if ( ! this . props . account ) {
return (
< Result
status = "403"
title = "403 Unauthorized"
subTitle = { i18next . t ( "general:Sorry, you do not have permission to access this page or logged in status invalid." ) }
2023-07-07 12:30:07 +08:00
extra = { < a href = "/web/public" > < Button type = "primary" > { i18next . t ( "general:Back Home" ) } < / B u t t o n > < / a > }
2023-05-05 21:23:59 +08:00
/ >
) ;
}
return (
< Row >
< Col span = { 24 } style = { { justifyContent : "center" } } >
< Row >
< Col span = { 24 } >
2023-06-24 18:39:54 +08:00
< p style = { { textAlign : "center" , fontSize : "28px" } } >
{ i18next . t ( "mfa:Protect your account with Multi-factor authentication" ) } < / p >
< p style = { { textAlign : "center" , fontSize : "16px" , marginTop : "10px" } } > { i18next . t ( "mfa:Each time you sign in to your Account, you'll need your password and a authentication code" ) } < / p >
2023-05-05 21:23:59 +08:00
< / C o l >
< / R o w >
2024-02-06 20:17:59 +08:00
< Spin spinning = { this . state . loading } >
< Steps current = { this . state . current }
items = { [
{ title : i18next . t ( "mfa:Verify Password" ) , icon : < UserOutlined / > } ,
{ title : i18next . t ( "mfa:Verify Code" ) , icon : < KeyOutlined / > } ,
{ title : i18next . t ( "general:Enable" ) , icon : < CheckOutlined / > } ,
] }
style = { { width : "90%" , maxWidth : "500px" , margin : "auto" , marginTop : "50px" ,
} } >
< / S t e p s >
< / S p i n >
2023-05-05 21:23:59 +08:00
< / C o l >
< Col span = { 24 } style = { { display : "flex" , justifyContent : "center" } } >
2023-05-17 01:13:13 +08:00
< div style = { { marginTop : "10px" , textAlign : "center" } } >
{ this . renderStep ( ) }
< / d i v >
2023-05-05 21:23:59 +08:00
< / C o l >
< / R o w >
) ;
}
}
2023-07-07 12:30:07 +08:00
export default withRouter ( MfaSetupPage ) ;