// 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. import React, {useState} from "react"; import {Button, Col, Form, Input, Result, Row, Steps} from "antd"; import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as Setting from "../Setting"; import i18next from "i18next"; import * as MfaBackend from "../backend/MfaBackend"; import {CheckOutlined, KeyOutlined, LockOutlined, UserOutlined} from "@ant-design/icons"; import * as UserBackend from "../backend/UserBackend"; import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaSetup} from "./MfaVerifyForm"; export const EmailMfaType = "email"; export const SmsMfaType = "sms"; export const TotpMfaType = "app"; export const RecoveryMfaType = "recovery"; function CheckPasswordForm({user, onSuccess, onFail}) { const [form] = Form.useForm(); const onFinish = ({password}) => { const data = {...user, password}; UserBackend.checkUserPassword(data) .then((res) => { if (res.status === "ok") { onSuccess(res); } else { onFail(res); } }) .finally(() => { form.setFieldsValue({password: ""}); }); }; return (
} placeholder={i18next.t("general:Password")} />
); } export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) { const [form] = Form.useForm(); const onFinish = ({passcode}) => { const data = {passcode, mfaType: mfaProps.mfaType, ...user}; MfaBackend.MfaSetupVerify(data) .then((res) => { if (res.status === "ok") { onSuccess(res); } else { onFail(res); } }) .catch((error) => { Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`); }) .finally(() => { form.setFieldsValue({passcode: ""}); }); }; if (mfaProps === undefined || mfaProps === null) { return
; } if (mfaProps.mfaType === SmsMfaType || mfaProps.mfaType === EmailMfaType) { return ; } else if (mfaProps.mfaType === TotpMfaType) { return ; } else { return
; } } function EnableMfaForm({user, mfaType, recoveryCodes, onSuccess, onFail}) { const [loading, setLoading] = useState(false); const requestEnableTotp = () => { const data = { mfaType, ...user, }; setLoading(true); MfaBackend.MfaSetupEnable(data).then(res => { if (res.status === "ok") { onSuccess(res); } else { onFail(res); } } ).finally(() => { setLoading(false); }); }; return (

{i18next.t("mfa:Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code")}


{recoveryCodes[0]}
); } class MfaSetupPage extends React.Component { constructor(props) { super(props); this.state = { account: props.account, application: this.props.application ?? null, applicationName: props.account.signupApplication ?? "", isAuthenticated: props.isAuthenticated ?? false, isPromptPage: props.isPromptPage, redirectUri: props.redirectUri, current: props.current ?? 0, mfaType: props.mfaType ?? new URLSearchParams(props.location?.search)?.get("mfaType") ?? SmsMfaType, mfaProps: null, }; } componentDidMount() { this.getApplication(); } componentDidUpdate(prevProps, prevState, snapshot) { if (this.state.isAuthenticated === true && (this.state.mfaProps === null || this.state.mfaType !== prevState.mfaType)) { MfaBackend.MfaSetupInitiate({ mfaType: this.state.mfaType, ...this.getUser(), }).then((res) => { if (res.status === "ok") { this.setState({ mfaProps: res.data, }); } else { Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA")); } }); } } getApplication() { if (this.state.application !== null) { return; } ApplicationBackend.getApplication("admin", this.state.applicationName) .then((application) => { if (application !== null) { this.setState({ application: application, }); } else { Setting.showMessage("error", i18next.t("mfa:Failed to get application")); } }); } getUser() { return { name: this.state.account.name, owner: this.state.account.owner, }; } renderStep() { switch (this.state.current) { case 0: return ( { this.setState({ current: this.state.current + 1, isAuthenticated: true, }); }} onFail={(res) => { Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA")); }} /> ); case 1: if (!this.state.isAuthenticated) { return null; } return (
{ this.setState({ current: this.state.current + 1, }); }} onFail={(res) => { Setting.showMessage("error", i18next.t("general:Failed to verify")); }} /> {(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null : } { (this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null : } { (this.state.mfaType === TotpMfaType) ? null : }
); case 2: if (!this.state.isAuthenticated) { return null; } return ( { Setting.showMessage("success", i18next.t("general:Enabled successfully")); if (this.state.isPromptPage && this.state.redirectUri) { Setting.goToLink(this.state.redirectUri); } else { Setting.goToLink("/account"); } }} onFail={(res) => { Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`); }} /> ); default: return null; } } render() { if (!this.props.account) { return ( } /> ); } return (

{i18next.t("mfa:Protect your account with Multi-factor authentication")}

{i18next.t("mfa:Each time you sign in to your Account, you'll need your password and a authentication code")}

}, {title: i18next.t("mfa:Verify Code"), icon: }, {title: i18next.t("general:Enable"), icon: }, ]} style={{width: "90%", maxWidth: "500px", margin: "auto", marginTop: "50px", }} >
{this.renderStep()}
); } } export default MfaSetupPage;