mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-24 07:03:21 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
7aa0b2e63f | |||
a39b121280 | |||
feef4cc242 | |||
1b5ef53655 | |||
18d639cca2 | |||
3ac5aad648 |
@ -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"},
|
||||
|
@ -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"));
|
||||
|
||||
@ -877,6 +878,10 @@ 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"
|
||||
@ -1204,6 +1209,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 +1231,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>
|
||||
|
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;
|
@ -82,7 +82,7 @@ export function renderPasswordPopover(options, password) {
|
||||
}
|
||||
|
||||
export function checkPasswordComplexity(password, options) {
|
||||
if (password.length === 0) {
|
||||
if (!password?.length) {
|
||||
return i18next.t("login:Please input your password!");
|
||||
}
|
||||
|
||||
|
@ -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 = [
|
||||
{
|
||||
|
Reference in New Issue
Block a user