mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-23 14:33:28 +08:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
d867afdd70 | |||
a92430e8fd | |||
447cb70553 | |||
e05fbec739 | |||
65ab36f073 | |||
d027e07383 | |||
d3c718b577 | |||
ea68e6c2dc | |||
7aa0b2e63f | |||
a39b121280 | |||
feef4cc242 | |||
1b5ef53655 |
@ -286,8 +286,7 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
}
|
||||
|
||||
if application.HasPromptPage() && user.Type == "normal-user" {
|
||||
// The prompt page needs the user to be signed in
|
||||
if user.Type == "normal-user" {
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
|
@ -38,9 +38,20 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||
if salt == "" {
|
||||
return getMd5HexDigest(password)
|
||||
}
|
||||
|
||||
return getMd5HexDigest(getMd5HexDigest(password) + salt)
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||
// For backward-compatibility
|
||||
if salt == "" {
|
||||
if hashedPwd == cm.GetHashedPassword(getMd5HexDigest(plainPwd), salt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||
}
|
||||
|
@ -38,9 +38,20 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||
if salt == "" {
|
||||
return getSha256HexDigest(password)
|
||||
}
|
||||
|
||||
return getSha256HexDigest(getSha256HexDigest(password) + salt)
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||
// For backward-compatibility
|
||||
if salt == "" {
|
||||
if hashedPwd == cm.GetHashedPassword(getSha256HexDigest(plainPwd), salt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||
}
|
||||
|
@ -38,9 +38,20 @@ func NewSha512SaltCredManager() *Sha512SaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||
if salt == "" {
|
||||
return getSha512HexDigest(password)
|
||||
}
|
||||
|
||||
return getSha512HexDigest(getSha512HexDigest(password) + salt)
|
||||
}
|
||||
|
||||
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||
// For backward-compatibility
|
||||
if salt == "" {
|
||||
if hashedPwd == cm.GetHashedPassword(getSha512HexDigest(plainPwd), salt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
||||
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
||||
ResponseModesSupported: []string{"query", "fragment"},
|
||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IdTokenSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512"},
|
||||
|
@ -81,12 +81,12 @@ type Organization struct {
|
||||
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
||||
EnableTour bool `json:"enableTour"`
|
||||
IpRestriction string `json:"ipRestriction"`
|
||||
NavItems []string `xorm:"varchar(1000)" json:"navItems"`
|
||||
WidgetItems []string `xorm:"varchar(1000)" json:"widgetItems"`
|
||||
NavItems []string `xorm:"mediumtext" json:"navItems"`
|
||||
WidgetItems []string `xorm:"mediumtext" json:"widgetItems"`
|
||||
|
||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||
MfaRememberInHours int `json:"mfaRememberInHours"`
|
||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||
AccountItems []*AccountItem `xorm:"mediumtext" json:"accountItems"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
|
||||
|
@ -190,7 +190,7 @@ type User struct {
|
||||
|
||||
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
|
||||
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes"`
|
||||
RecoveryCodes []string `xorm:"mediumtext" json:"recoveryCodes"`
|
||||
TotpSecret string `xorm:"varchar(100)" json:"totpSecret"`
|
||||
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
|
||||
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
|
||||
@ -204,7 +204,7 @@ type User struct {
|
||||
|
||||
Roles []*Role `json:"roles"`
|
||||
Permissions []*Permission `json:"permissions"`
|
||||
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
|
||||
Groups []string `xorm:"mediumtext" json:"groups"`
|
||||
|
||||
LastChangePasswordTime string `xorm:"varchar(100)" json:"lastChangePasswordTime"`
|
||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||
|
@ -247,7 +247,9 @@ class App extends Component {
|
||||
account.organization = res.data2;
|
||||
accessToken = res.data.accessToken;
|
||||
|
||||
if (!localStorage.getItem("language")) {
|
||||
this.setLanguage(account);
|
||||
}
|
||||
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
|
||||
setTourLogo(account.organization.logo);
|
||||
setOrgIsTourVisible(account.organization.enableTour);
|
||||
|
@ -1237,7 +1237,7 @@ class ApplicationEditPage extends React.Component {
|
||||
submitApplicationEdit(exitAfterSave) {
|
||||
const application = Setting.deepCopy(this.state.application);
|
||||
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
|
||||
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP", "Face ID"].includes(signinMethod.name));
|
||||
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP", "Face ID", "WeChat"].includes(signinMethod.name));
|
||||
|
||||
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
|
||||
.then((res) => {
|
||||
|
@ -208,10 +208,14 @@ let orgIsTourVisible = true;
|
||||
|
||||
export function setOrgIsTourVisible(visible) {
|
||||
orgIsTourVisible = visible;
|
||||
if (orgIsTourVisible === false) {
|
||||
setIsTourVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
export function setIsTourVisible(visible) {
|
||||
localStorage.setItem("isTourVisible", visible);
|
||||
window.dispatchEvent(new Event("storageTourChanged"));
|
||||
}
|
||||
|
||||
export function setTourLogo(tourLogoSrc) {
|
||||
@ -221,7 +225,7 @@ export function setTourLogo(tourLogoSrc) {
|
||||
}
|
||||
|
||||
export function getTourVisible() {
|
||||
return localStorage.getItem("isTourVisible") !== "false" && orgIsTourVisible;
|
||||
return localStorage.getItem("isTourVisible") !== "false";
|
||||
}
|
||||
|
||||
export function getNextButtonChild(nextPathName) {
|
||||
|
@ -38,6 +38,7 @@ import {RequiredMfa} from "./mfa/MfaAuthVerifyForm";
|
||||
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
|
||||
import * as ProviderButton from "./ProviderButton";
|
||||
import {goToLink} from "../Setting";
|
||||
import WeChatLoginPanel from "./WeChatLoginPanel";
|
||||
const FaceRecognitionCommonModal = lazy(() => import("../common/modal/FaceRecognitionCommonModal"));
|
||||
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
|
||||
|
||||
@ -346,7 +347,7 @@ class LoginPage extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.data2) {
|
||||
if (resp.data3) {
|
||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||
Setting.goToLinkSoft(ths, `/forget/${application.name}`);
|
||||
return;
|
||||
@ -436,6 +437,9 @@ class LoginPage extends React.Component {
|
||||
values["password"] = passwordCipher;
|
||||
}
|
||||
const captchaRule = this.getCaptchaRule(this.getApplicationObj());
|
||||
const application = this.getApplicationObj();
|
||||
const noModal = application?.signinItems.map(signinItem => signinItem.name === "Captcha" && signinItem.rule === "inline").includes(true);
|
||||
if (!noModal) {
|
||||
if (captchaRule === CaptchaRule.Always) {
|
||||
this.setState({
|
||||
openCaptchaModal: true,
|
||||
@ -449,6 +453,11 @@ class LoginPage extends React.Component {
|
||||
this.checkCaptchaStatus(values);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
values["captchaType"] = this.state?.captchaValues?.captchaType;
|
||||
values["captchaToken"] = this.state?.captchaValues?.captchaToken;
|
||||
values["clientSecret"] = this.state?.captchaValues?.clientSecret;
|
||||
}
|
||||
}
|
||||
this.login(values);
|
||||
}
|
||||
@ -774,7 +783,7 @@ class LoginPage extends React.Component {
|
||||
</>
|
||||
}
|
||||
{
|
||||
this.renderCaptchaModal(application)
|
||||
application?.signinItems.map(signinItem => signinItem.name === "Captcha" && signinItem.rule === "inline").includes(true) ? null : this.renderCaptchaModal(application, false)
|
||||
}
|
||||
</Form.Item>
|
||||
);
|
||||
@ -818,6 +827,8 @@ class LoginPage extends React.Component {
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Captcha" && signinItem.rule === "inline") {
|
||||
return this.renderCaptchaModal(application, true);
|
||||
} else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
|
||||
return (
|
||||
<div key={resultItemKey} dangerouslySetInnerHTML={{__html: signinItem.customCss}} />
|
||||
@ -877,13 +888,17 @@ class LoginPage extends React.Component {
|
||||
loginWidth += 10;
|
||||
}
|
||||
|
||||
if (this.state.loginMethod === "wechat") {
|
||||
return (<WeChatLoginPanel application={application} renderFormItem={this.renderFormItem.bind(this)} loginMethod={this.state.loginMethod} loginWidth={loginWidth} renderMethodChoiceBox={this.renderMethodChoiceBox.bind(this)} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="normal_login"
|
||||
initialValues={{
|
||||
organization: application.organization,
|
||||
application: application.name,
|
||||
autoSignin: true,
|
||||
autoSignin: !application?.signinItems.map(signinItem => signinItem.name === "Forgot password?" && signinItem.rule === "Auto sign in - False")?.includes(true),
|
||||
username: Conf.ShowGithubCorner ? "admin" : "",
|
||||
password: Conf.ShowGithubCorner ? "123" : "",
|
||||
}}
|
||||
@ -959,7 +974,7 @@ class LoginPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderCaptchaModal(application) {
|
||||
renderCaptchaModal(application, noModal) {
|
||||
if (this.getCaptchaRule(this.getApplicationObj()) === CaptchaRule.Never) {
|
||||
return null;
|
||||
}
|
||||
@ -988,6 +1003,12 @@ class LoginPage extends React.Component {
|
||||
owner={provider.owner}
|
||||
name={provider.name}
|
||||
visible={this.state.openCaptchaModal}
|
||||
noModal={noModal}
|
||||
onUpdateToken={(captchaType, captchaToken, clientSecret) => {
|
||||
this.setState({captchaValues: {
|
||||
captchaType, captchaToken, clientSecret,
|
||||
}});
|
||||
}}
|
||||
onOk={(captchaType, captchaToken, clientSecret) => {
|
||||
const values = this.state.values;
|
||||
values["captchaType"] = captchaType;
|
||||
@ -1204,6 +1225,7 @@ class LoginPage extends React.Component {
|
||||
[generateItemKey("WebAuthn", "None"), {label: i18next.t("login:WebAuthn"), key: "webAuthn"}],
|
||||
[generateItemKey("LDAP", "None"), {label: i18next.t("login:LDAP"), key: "ldap"}],
|
||||
[generateItemKey("Face ID", "None"), {label: i18next.t("login:Face ID"), key: "faceId"}],
|
||||
[generateItemKey("WeChat", "None"), {label: i18next.t("login:WeChat"), key: "wechat"}],
|
||||
]);
|
||||
|
||||
application?.signinMethods?.forEach((signinMethod) => {
|
||||
@ -1225,7 +1247,7 @@ class LoginPage extends React.Component {
|
||||
if (items.length > 1) {
|
||||
return (
|
||||
<div>
|
||||
<Tabs className="signin-methods" items={items} size={"small"} defaultActiveKey={this.getDefaultLoginMethod(application)} onChange={(key) => {
|
||||
<Tabs className="signin-methods" items={items} size={"small"} activeKey={this.state.loginMethod} onChange={(key) => {
|
||||
this.setState({loginMethod: key});
|
||||
}} centered>
|
||||
</Tabs>
|
||||
|
@ -18,6 +18,7 @@ import i18next from "i18next";
|
||||
import {authConfig} from "./Auth";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as Setting from "../Setting";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
|
||||
class ResultPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -60,6 +61,22 @@ class ResultPage extends React.Component {
|
||||
this.props.onUpdateApplication(application);
|
||||
}
|
||||
|
||||
handleSignIn = () => {
|
||||
AuthBackend.getAccount()
|
||||
.then((res) => {
|
||||
if (res.status === "ok" && res.data) {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
window.location.href = linkInStorage;
|
||||
} else {
|
||||
Setting.goToLink("/");
|
||||
}
|
||||
} else {
|
||||
Setting.redirectToLoginPage(this.state.application, this.props.history);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const application = this.state.application;
|
||||
|
||||
@ -89,14 +106,7 @@ class ResultPage extends React.Component {
|
||||
title={i18next.t("signup:Your account has been created!")}
|
||||
subTitle={i18next.t("signup:Please click the below button to sign in")}
|
||||
extra={[
|
||||
<Button type="primary" key="login" onClick={() => {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLinkSoft(this, linkInStorage);
|
||||
} else {
|
||||
Setting.redirectToLoginPage(application, this.props.history);
|
||||
}
|
||||
}}>
|
||||
<Button type="primary" key="login" onClick={this.handleSignIn}>
|
||||
{i18next.t("login:Sign In")}
|
||||
</Button>,
|
||||
]}
|
||||
|
106
web/src/auth/WeChatLoginPanel.js
Normal file
106
web/src/auth/WeChatLoginPanel.js
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2025 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 from "react";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import i18next from "i18next";
|
||||
import * as Util from "./Util";
|
||||
|
||||
class WeChatLoginPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
qrCode: null,
|
||||
loading: false,
|
||||
ticket: null,
|
||||
};
|
||||
this.pollingTimer = null;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.fetchQrCode();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.loginMethod === "wechat" && prevProps.loginMethod !== "wechat") {
|
||||
this.fetchQrCode();
|
||||
}
|
||||
if (prevProps.loginMethod === "wechat" && this.props.loginMethod !== "wechat") {
|
||||
this.setState({qrCode: null, loading: false, ticket: null});
|
||||
this.clearPolling();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearPolling();
|
||||
}
|
||||
|
||||
clearPolling() {
|
||||
if (this.pollingTimer) {
|
||||
clearInterval(this.pollingTimer);
|
||||
this.pollingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
fetchQrCode() {
|
||||
const {application} = this.props;
|
||||
const wechatProviderItem = application?.providers?.find(p => p.provider?.type === "WeChat");
|
||||
if (wechatProviderItem) {
|
||||
this.setState({loading: true, qrCode: null, ticket: null});
|
||||
AuthBackend.getWechatQRCode(`${wechatProviderItem.provider.owner}/${wechatProviderItem.provider.name}`).then(res => {
|
||||
if (res.status === "ok" && res.data) {
|
||||
this.setState({qrCode: res.data, loading: false, ticket: res.data2});
|
||||
this.clearPolling();
|
||||
this.pollingTimer = setInterval(() => {
|
||||
Util.getEvent(application, wechatProviderItem.provider, res.data2, "signup");
|
||||
}, 1000);
|
||||
} else {
|
||||
this.setState({qrCode: null, loading: false, ticket: null});
|
||||
this.clearPolling();
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({qrCode: null, loading: false, ticket: null});
|
||||
this.clearPolling();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {application, loginWidth = 320} = this.props;
|
||||
const {loading, qrCode} = this.state;
|
||||
return (
|
||||
<div style={{width: loginWidth, margin: "0 auto", textAlign: "center", marginTop: 16}}>
|
||||
{application.signinItems?.filter(item => item.name === "Logo").map(signinItem => this.props.renderFormItem(application, signinItem))}
|
||||
{this.props.renderMethodChoiceBox()}
|
||||
{application.signinItems?.filter(item => item.name === "Languages").map(signinItem => this.props.renderFormItem(application, signinItem))}
|
||||
{loading ? (
|
||||
<div style={{marginTop: 16}}>
|
||||
<span>{i18next.t("login:Loading...")}</span>
|
||||
</div>
|
||||
) : qrCode ? (
|
||||
<div style={{marginTop: 2}}>
|
||||
<img src={`data:image/png;base64,${qrCode}`} alt="WeChat QR code" style={{width: 250, height: 250}} />
|
||||
<div style={{marginTop: 8}}>
|
||||
<a onClick={e => {e.preventDefault(); this.fetchQrCode();}}>
|
||||
{i18next.t("login:Refresh")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WeChatLoginPanel;
|
@ -31,17 +31,11 @@ class OpenTour extends React.Component {
|
||||
return TourConfig.TourUrlList.indexOf(path) !== -1 || path === "";
|
||||
};
|
||||
|
||||
handleTourClick = () => {
|
||||
TourConfig.setIsTourVisible(true);
|
||||
const event = new Event("storageTourChanged");
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
this.canTour() ?
|
||||
<Tooltip title="Click to open tour">
|
||||
<div className="select-box" style={{display: Setting.isMobile() ? "none" : null, ...this.props.style}} onClick={this.handleTourClick}>
|
||||
<div className="select-box" style={{display: Setting.isMobile() ? "none" : null, ...this.props.style}} onClick={() => TourConfig.setIsTourVisible(true)} >
|
||||
<QuestionCircleOutlined style={{fontSize: "24px"}} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -20,7 +20,7 @@ import {CaptchaWidget} from "../CaptchaWidget";
|
||||
import {SafetyOutlined} from "@ant-design/icons";
|
||||
|
||||
export const CaptchaModal = (props) => {
|
||||
const {owner, name, visible, onOk, onCancel, isCurrentProvider} = props;
|
||||
const {owner, name, visible, onOk, onUpdateToken, onCancel, isCurrentProvider, noModal} = props;
|
||||
|
||||
const [captchaType, setCaptchaType] = React.useState("none");
|
||||
const [clientId, setClientId] = React.useState("");
|
||||
@ -36,16 +36,16 @@ export const CaptchaModal = (props) => {
|
||||
const defaultInputRef = React.useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
if (visible || noModal) {
|
||||
loadCaptcha();
|
||||
} else {
|
||||
handleCancel();
|
||||
setOpen(false);
|
||||
}
|
||||
}, [visible]);
|
||||
}, [visible, noModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (captchaToken !== "" && captchaType !== "Default") {
|
||||
if (captchaToken !== "" && captchaType !== "Default" && !noModal) {
|
||||
handleOk();
|
||||
}
|
||||
}, [captchaToken]);
|
||||
@ -81,6 +81,36 @@ export const CaptchaModal = (props) => {
|
||||
};
|
||||
|
||||
const renderDefaultCaptcha = () => {
|
||||
if (noModal) {
|
||||
return (
|
||||
<Row style={{textAlign: "center"}}>
|
||||
<Col
|
||||
style={{flex: noModal ? "70%" : "100%"}}>
|
||||
<Input
|
||||
ref={defaultInputRef}
|
||||
value={captchaToken}
|
||||
prefix={<SafetyOutlined />}
|
||||
placeholder={i18next.t("general:Captcha")}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</Col>
|
||||
<Col
|
||||
style={{
|
||||
flex: noModal ? "30%" : "100%",
|
||||
}}
|
||||
>
|
||||
<img src={`data:image/png;base64,${captchaImg}`}
|
||||
onClick={loadCaptcha}
|
||||
style={{
|
||||
borderRadius: "5px",
|
||||
border: "1px solid #ccc",
|
||||
marginBottom: "20px",
|
||||
width: "100%",
|
||||
}} alt="captcha" />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Col style={{textAlign: "center"}}>
|
||||
<div style={{display: "inline-block"}}>
|
||||
@ -113,6 +143,9 @@ export const CaptchaModal = (props) => {
|
||||
|
||||
const onChange = (token) => {
|
||||
setCaptchaToken(token);
|
||||
if (noModal) {
|
||||
onUpdateToken?.(captchaType, token, clientSecret);
|
||||
}
|
||||
};
|
||||
|
||||
const renderCaptcha = () => {
|
||||
@ -153,6 +186,10 @@ export const CaptchaModal = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
if (noModal) {
|
||||
return renderCaptcha();
|
||||
|
||||
} else {
|
||||
return (
|
||||
<Modal
|
||||
closable={true}
|
||||
@ -175,6 +212,7 @@ export const CaptchaModal = (props) => {
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const CaptchaRule = {
|
||||
|
@ -72,6 +72,7 @@ class SigninMethodTable extends React.Component {
|
||||
{name: "WebAuthn", displayName: i18next.t("login:WebAuthn")},
|
||||
{name: "LDAP", displayName: i18next.t("login:LDAP")},
|
||||
{name: "Face ID", displayName: i18next.t("login:Face ID")},
|
||||
{name: "WeChat", displayName: i18next.t("login:WeChat")},
|
||||
];
|
||||
const columns = [
|
||||
{
|
||||
|
@ -49,6 +49,9 @@ class SigninTable extends React.Component {
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
if (key === "name" && value === "Captcha") {
|
||||
table[index]["rule"] = "pop up";
|
||||
}
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
@ -114,6 +117,8 @@ class SigninTable extends React.Component {
|
||||
{name: "Forgot password?", displayName: i18next.t("login:Forgot password?")},
|
||||
{name: "Login button", displayName: i18next.t("login:Signin button")},
|
||||
{name: "Signup link", displayName: i18next.t("general:Signup link")},
|
||||
{name: "Captcha", displayName: i18next.t("general:Captcha")},
|
||||
{name: "Auto sign in", displayName: i18next.t("login:Auto sign in")},
|
||||
];
|
||||
|
||||
const getItemDisplayName = (text) => {
|
||||
@ -249,6 +254,19 @@ class SigninTable extends React.Component {
|
||||
{id: "small", name: i18next.t("application:Small icon")},
|
||||
];
|
||||
}
|
||||
if (record.name === "Captcha") {
|
||||
options = [
|
||||
{id: "pop up", name: i18next.t("application:Pop up")},
|
||||
{id: "inline", name: i18next.t("application:Inline")},
|
||||
];
|
||||
}
|
||||
if (record.name === "Forgot password?") {
|
||||
options = [
|
||||
{id: "None", name: `${i18next.t("login:Auto sign in")} - ${i18next.t("general:True")}`},
|
||||
{id: "Auto sign in - False", name: `${i18next.t("login:Auto sign in")} - ${i18next.t("general:False")}`},
|
||||
];
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
Reference in New Issue
Block a user