feat: allow user use other mfaType in mfa step and skip redundant MFA verification (#3499)

* feat: allow user use other mfaType in mfa step and skip redundant MFA verification

* feat: improve format
This commit is contained in:
DacongDA 2025-01-21 20:16:18 +08:00 committed by GitHub
parent 4b0a2fdbfc
commit e1b3b0ac6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 31 deletions

View File

@ -331,6 +331,8 @@ func (c *ApiController) Login() {
return
}
verificationType := ""
if authForm.Username != "" {
if authForm.Type == ResponseTypeLogin {
if c.GetSessionUsername() != "" {
@ -425,6 +427,12 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error(), nil)
return
}
if verificationCodeType == object.VerifyTypePhone {
verificationType = "sms"
} else {
verificationType = "email"
}
} else {
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
@ -524,8 +532,19 @@ func (c *ApiController) Login() {
if user.IsMfaEnabled() {
c.setMfaUserSession(user.GetId())
c.ResponseOk(object.NextMfa, user.GetPreferredMfaProps(true))
return
mfaList := object.GetAllMfaProps(user, true)
mfaAllowList := []*object.MfaProps{}
for _, prop := range mfaList {
if prop.MfaType == verificationType || !prop.Enabled {
continue
}
mfaAllowList = append(mfaAllowList, prop)
}
if len(mfaAllowList) >= 1 {
c.SetSession("verificationCodeType", verificationType)
c.ResponseOk(object.NextMfa, mfaAllowList)
return
}
}
resp = c.HandleLoggedIn(application, user, &authForm)
@ -866,8 +885,12 @@ func (c *ApiController) Login() {
}
if authForm.Passcode != "" {
if authForm.MfaType == c.GetSession("verificationCodeType") {
c.ResponseError("Invalid multi-factor authentication type")
return
}
user.CountryCode = user.GetCountryCode(user.CountryCode)
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetMfaProps(authForm.MfaType, false))
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")
return
@ -878,6 +901,7 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error())
return
}
c.SetSession("verificationCodeType", "")
} else if authForm.RecoveryCode != "" {
err = object.MfaRecover(user, authForm.RecoveryCode)
if err != nil {

View File

@ -37,6 +37,7 @@ import RedirectForm from "../common/RedirectForm";
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
import * as ProviderButton from "./ProviderButton";
import {EmailMfaType, SmsMfaType, TotpMfaType} from "./MfaSetupPage";
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
class LoginPage extends React.Component {
@ -440,19 +441,12 @@ class LoginPage extends React.Component {
if (res.status === "ok") {
if (res.data === NextMfa) {
this.setState({
getVerifyTotp: () => {
return (
<MfaAuthVerifyForm
mfaProps={res.data2}
formValues={values}
authParams={casParams}
application={this.getApplicationObj()}
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
}}
onSuccess={(res) => loginHandler(res)}
/>);
},
mfaProps: res.data2,
selectedMfaProp: this.getPreferredMfaProp(res.data2),
}, () => {
this.setState({
getVerifyTotp: () => this.renderMfaAuthVerifyForm(values, casParams, loginHandler),
});
});
} else {
loginHandler(res);
@ -513,19 +507,12 @@ class LoginPage extends React.Component {
if (res.status === "ok") {
if (res.data === NextMfa) {
this.setState({
getVerifyTotp: () => {
return (
<MfaAuthVerifyForm
mfaProps={res.data2}
formValues={values}
authParams={oAuthParams}
application={this.getApplicationObj()}
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
}}
onSuccess={(res) => loginHandler(res)}
/>);
},
mfaProps: res.data2,
selectedMfaProp: this.getPreferredMfaProp(res.data2),
}, () => {
this.setState({
getVerifyTotp: () => this.renderMfaAuthVerifyForm(values, oAuthParams, loginHandler),
});
});
} else if (res.data === "SelectPlan") {
// paid-user does not have active or pending subscription, go to application default pricing page to select-plan
@ -545,6 +532,49 @@ class LoginPage extends React.Component {
}
}
renderMfaAuthVerifyForm(values, authParams, onSuccess) {
return (
<div>
<MfaAuthVerifyForm
mfaProps={this.state.selectedMfaProp}
formValues={values}
authParams={authParams}
application={this.getApplicationObj()}
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
}}
onSuccess={(res) => onSuccess(res)}
/>
<div>
{
this.state.mfaProps.map((mfa) => {
if (this.state.selectedMfaProp.mfaType === mfa.mfaType) {return null;}
let mfaI18n = "";
switch (mfa.mfaType) {
case SmsMfaType: mfaI18n = i18next.t("mfa:Use SMS"); break;
case TotpMfaType: mfaI18n = i18next.t("mfa:Use Authenticator App"); break ;
case EmailMfaType: mfaI18n = i18next.t("mfa:Use Email") ;break;
}
return <div key={mfa.mfaType}><Button type={"link"} onClick={() => {
this.setState({
selectedMfaProp: mfa,
});
}}>{mfaI18n}</Button></div>;
})
}
</div>
</div>);
}
getPreferredMfaProp(mfaProps) {
for (const i in mfaProps) {
if (mfaProps[i].isPreffered) {
return mfaProps[i];
}
}
return mfaProps[0];
}
isProviderVisible(providerItem) {
if (this.state.mode === "signup") {
return Setting.isProviderVisibleForSignUp(providerItem);

View File

@ -33,7 +33,8 @@ export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application
const verify = ({passcode}) => {
setLoading(true);
const values = {...formValues, passcode, mfaType};
const values = {...formValues, passcode};
values["mfaType"] = mfaProps.mfaType;
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
loginFunction(values, authParams).then((res) => {
if (res.status === "ok") {
@ -71,7 +72,7 @@ export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
{i18next.t("mfa:Multi-factor authentication")}
</div>
{mfaType === SmsMfaType || mfaType === EmailMfaType ? (
{mfaProps.mfaType === SmsMfaType || mfaProps.mfaType === EmailMfaType ? (
<Fragment>
<div style={{marginBottom: 24}}>
{i18next.t("mfa:You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue")}