mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 01:13:41 +08:00
Compare commits
1 Commits
v1.862.0
...
revert-361
Author | SHA1 | Date | |
---|---|---|---|
a79a2e7e55 |
@ -249,10 +249,6 @@ func (c *ApiController) Signup() {
|
||||
user.Groups = []string{invitation.SignupGroup}
|
||||
}
|
||||
|
||||
if application.DefaultGroup != "" && user.Groups == nil {
|
||||
user.Groups = []string{application.DefaultGroup}
|
||||
}
|
||||
|
||||
affected, err := object.AddUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@ -598,9 +598,6 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s does not exist"), authForm.Provider))
|
||||
}
|
||||
|
||||
providerItem := application.GetProviderItem(provider.Name)
|
||||
if !providerItem.IsProviderVisible() {
|
||||
@ -989,18 +986,6 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
if authForm.Language != "" {
|
||||
user := c.getCurrentUser()
|
||||
if user != nil {
|
||||
user.Language = authForm.Language
|
||||
_, err = object.UpdateUser(user.GetId(), user, []string{"language"}, user.IsAdmin)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ type AuthForm struct {
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Language string `json:"language"`
|
||||
Region string `json:"region"`
|
||||
InvitationCode string `json:"invitationCode"`
|
||||
|
||||
|
@ -71,7 +71,6 @@ type Application struct {
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
|
||||
HeaderHtml string `xorm:"mediumtext" json:"headerHtml"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
@ -98,30 +97,29 @@ type Application struct {
|
||||
IsShared bool `json:"isShared"`
|
||||
IpRestriction string `json:"ipRestriction"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||
TokenSigningMethod string `xorm:"varchar(100)" json:"tokenSigningMethod"`
|
||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||
ExpireInHours int `json:"expireInHours"`
|
||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
|
||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
||||
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
||||
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
FooterHtml string `xorm:"mediumtext" json:"footerHtml"`
|
||||
FormCss string `xorm:"text" json:"formCss"`
|
||||
FormCssMobile string `xorm:"text" json:"formCssMobile"`
|
||||
FormOffset int `json:"formOffset"`
|
||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
FormBackgroundUrlMobile string `xorm:"varchar(200)" json:"formBackgroundUrlMobile"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||
TokenSigningMethod string `xorm:"varchar(100)" json:"tokenSigningMethod"`
|
||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||
ExpireInHours int `json:"expireInHours"`
|
||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
|
||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
||||
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
||||
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
FooterHtml string `xorm:"mediumtext" json:"footerHtml"`
|
||||
FormCss string `xorm:"text" json:"formCss"`
|
||||
FormCssMobile string `xorm:"text" json:"formCssMobile"`
|
||||
FormOffset int `json:"formOffset"`
|
||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
|
||||
FailedSigninLimit int `json:"failedSigninLimit"`
|
||||
FailedSigninFrozenTime int `json:"failedSigninFrozenTime"`
|
||||
|
@ -38,7 +38,6 @@ type Webhook struct {
|
||||
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
||||
Events []string `xorm:"varchar(1000)" json:"events"`
|
||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||
IsUserExtended bool `json:"isUserExtended"`
|
||||
SingleOrgOnly bool `json:"singleOrgOnly"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
|
@ -17,7 +17,6 @@ package object
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -26,43 +25,17 @@ import (
|
||||
|
||||
func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *User) (int, string, error) {
|
||||
client := &http.Client{}
|
||||
userMap := make(map[string]interface{})
|
||||
var body io.Reader
|
||||
|
||||
if webhook.TokenFields != nil && len(webhook.TokenFields) > 0 && extendedUser != nil {
|
||||
userValue := reflect.ValueOf(extendedUser).Elem()
|
||||
|
||||
for _, field := range webhook.TokenFields {
|
||||
userField := userValue.FieldByName(field)
|
||||
if userField.IsValid() {
|
||||
newfield := util.SnakeToCamel(util.CamelToSnakeCase(field))
|
||||
userMap[newfield] = userField.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
type RecordEx struct {
|
||||
casvisorsdk.Record
|
||||
ExtendedUser map[string]interface{} `json:"extendedUser"`
|
||||
}
|
||||
|
||||
recordEx := &RecordEx{
|
||||
Record: *record,
|
||||
ExtendedUser: userMap,
|
||||
}
|
||||
|
||||
body = strings.NewReader(util.StructToJson(recordEx))
|
||||
} else {
|
||||
type RecordEx struct {
|
||||
casvisorsdk.Record
|
||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||
}
|
||||
recordEx := &RecordEx{
|
||||
Record: *record,
|
||||
ExtendedUser: extendedUser,
|
||||
}
|
||||
|
||||
body = strings.NewReader(util.StructToJson(recordEx))
|
||||
type RecordEx struct {
|
||||
casvisorsdk.Record
|
||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||
}
|
||||
recordEx := &RecordEx{
|
||||
Record: *record,
|
||||
ExtendedUser: extendedUser,
|
||||
}
|
||||
|
||||
body := strings.NewReader(util.StructToJson(recordEx))
|
||||
|
||||
req, err := http.NewRequest(webhook.Method, webhook.Url, body)
|
||||
if err != nil {
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -181,11 +180,7 @@ func (c *AirwallexClient) authRequest(method, url string, body interface{}) (map
|
||||
return nil, err
|
||||
}
|
||||
b, _ := json.Marshal(body)
|
||||
var reqBody io.Reader
|
||||
if method != "GET" {
|
||||
reqBody = bytes.NewBuffer(b)
|
||||
}
|
||||
req, _ := http.NewRequest(method, url, reqBody)
|
||||
req, _ := http.NewRequest(method, url, bytes.NewBuffer(b))
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := c.client.Do(req)
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, ConfigProvider, Input, InputNumber, Popover, Radio, Result, Row, Select, Space, Switch, Upload} from "antd";
|
||||
import {CopyOutlined, HolderOutlined, LinkOutlined, UploadOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||
import {Button, Card, Col, ConfigProvider, Input, InputNumber, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
|
||||
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@ -36,7 +36,6 @@ import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
|
||||
import SigninTable from "./table/SigninTable";
|
||||
import Editor from "./common/Editor";
|
||||
import * as GroupBackend from "./backend/GroupBackend";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -117,7 +116,6 @@ class ApplicationEditPage extends React.Component {
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getApplication();
|
||||
this.getOrganizations();
|
||||
this.getGroups();
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
@ -169,17 +167,6 @@ class ApplicationEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
GroupBackend.getGroups(this.state.organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
groups: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCerts(application) {
|
||||
let owner = application.organization;
|
||||
if (application.isShared) {
|
||||
@ -482,31 +469,6 @@ class ApplicationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.defaultGroup ?? []} onChange={(value => {
|
||||
this.updateApplicationField("defaultGroup", value);
|
||||
})}
|
||||
>
|
||||
<Option key={""} value={""}>
|
||||
<Space>
|
||||
{i18next.t("general:Default")}
|
||||
</Space>
|
||||
</Option>
|
||||
{
|
||||
this.state.groups?.map((group) => <Option key={group.name} value={`${group.owner}/${group.name}`}>
|
||||
<Space>
|
||||
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
|
||||
{group.displayName}
|
||||
</Space>
|
||||
</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} :
|
||||
@ -842,33 +804,6 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Background URL Mobile"), i18next.t("application:Background URL Mobile - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.formBackgroundUrlMobile} onChange={e => {
|
||||
this.updateApplicationField("formBackgroundUrlMobile", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<a target="_blank" rel="noreferrer" href={this.state.application.formBackgroundUrlMobile}>
|
||||
<img src={this.state.application.formBackgroundUrlMobile} alt={this.state.application.formBackgroundUrlMobile} height={90} style={{marginBottom: "20px"}} />
|
||||
</a>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Custom CSS"), i18next.t("application:Custom CSS - Tooltip"))} :
|
||||
|
@ -109,7 +109,7 @@ class EntryPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<CustomHead headerHtml={this.state.application?.headerHtml} />
|
||||
<div className={`${isDarkMode ? "loginBackgroundDark" : "loginBackground"}`}
|
||||
style={{backgroundImage: Setting.inIframe() ? null : (Setting.isMobile() ? `url(${this.state.application?.formBackgroundUrlMobile})` : `url(${this.state.application?.formBackgroundUrl})`)}}>
|
||||
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
||||
style={{width: "100%", margin: "0 auto", position: "absolute"}} />
|
||||
<Switch>
|
||||
|
@ -443,6 +443,8 @@ function ManagementPage(props) {
|
||||
return Setting.isMobile() || window.location.pathname.startsWith("/trees");
|
||||
}
|
||||
|
||||
const menuStyleRight = Setting.isAdminUser(props.account) && !Setting.isMobile() ? "calc(180px + 280px)" : "320px";
|
||||
|
||||
const onClose = () => {
|
||||
setMenuVisible(false);
|
||||
};
|
||||
@ -454,40 +456,34 @@ function ManagementPage(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EnableMfaNotification account={props.account} />
|
||||
<Header style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: "0", marginBottom: "4px", backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}} >
|
||||
{
|
||||
props.requiredEnableMfa || (Setting.isMobile() ? (
|
||||
<React.Fragment>
|
||||
<Drawer title={i18next.t("general:Close")} placement="left" open={menuVisible} onClose={onClose}>
|
||||
<Menu
|
||||
items={getMenuItems()}
|
||||
mode={"inline"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{lineHeight: "64px"}}
|
||||
onClick={onClose}
|
||||
>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Button icon={<BarsOutlined />} onClick={showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
// Padding 1px for Menu Item Highlight border
|
||||
<div style={{flex: 1, overflow: "hidden", paddingBottom: "1px"}}>
|
||||
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}} >
|
||||
{props.requiredEnableMfa || (Setting.isMobile() ?
|
||||
<React.Fragment>
|
||||
<Drawer title={i18next.t("general:Close")} placement="left" open={menuVisible} onClose={onClose}>
|
||||
<Menu
|
||||
onClick={onClose}
|
||||
items={getMenuItems()}
|
||||
mode={"horizontal"}
|
||||
mode={"inline"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
style={{lineHeight: "64px"}}
|
||||
onClick={onClose}
|
||||
>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Button icon={<BarsOutlined />} onClick={showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
</React.Fragment> :
|
||||
<Menu
|
||||
onClick={onClose}
|
||||
items={getMenuItems()}
|
||||
mode={"horizontal"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{position: "absolute", left: 0, right: menuStyleRight, backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
renderAccountMenu()
|
||||
}
|
||||
<div style={{flexShrink: 0}}>
|
||||
{renderAccountMenu()}
|
||||
</div>
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{isWithoutCard() ?
|
||||
|
@ -174,16 +174,7 @@ class WebhookEditPage extends React.Component {
|
||||
renderWebhook() {
|
||||
const preview = Setting.deepCopy(previewTemplate);
|
||||
if (this.state.webhook.isUserExtended) {
|
||||
if (this.state.webhook.tokenFields && this.state.webhook.tokenFields.length !== 0) {
|
||||
const extendedUser = {};
|
||||
this.state.webhook.tokenFields.forEach(field => {
|
||||
const fieldTrans = field.replace(field[0], field[0].toLowerCase());
|
||||
extendedUser[fieldTrans] = userTemplate[fieldTrans];
|
||||
});
|
||||
preview["extendedUser"] = extendedUser;
|
||||
} else {
|
||||
preview["extendedUser"] = userTemplate;
|
||||
}
|
||||
preview["extendedUser"] = userTemplate;
|
||||
}
|
||||
const previewText = JSON.stringify(preview, null, 2);
|
||||
|
||||
@ -304,18 +295,6 @@ class WebhookEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Extended user fields"), i18next.t("application:Extended user fields - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" showSearch style={{width: "100%"}} value={this.state.webhook.tokenFields} onChange={(value => {this.updateWebhookField("tokenFields", value);})}>
|
||||
{
|
||||
Setting.getUserCommonFields().map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||
|
@ -61,7 +61,6 @@ class LoginPage extends React.Component {
|
||||
isTermsOfUseVisible: false,
|
||||
termsOfUseContent: "",
|
||||
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
|
||||
userLang: null,
|
||||
};
|
||||
|
||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||
@ -416,7 +415,6 @@ class LoginPage extends React.Component {
|
||||
|
||||
login(values) {
|
||||
// here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
|
||||
values["language"] = this.state.userLang ?? "";
|
||||
if (this.state.type === "cas") {
|
||||
// CAS
|
||||
const casParams = Util.getCasParameters();
|
||||
@ -568,7 +566,7 @@ class LoginPage extends React.Component {
|
||||
return (
|
||||
<div key={resultItemKey} className="login-languages">
|
||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||
<LanguageSelect languages={application.organizationObj.languages} onClick={key => {this.setState({userLang: key});}} />
|
||||
<LanguageSelect languages={application.organizationObj.languages} />
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Signin methods") {
|
||||
@ -807,6 +805,7 @@ class LoginPage extends React.Component {
|
||||
<Form
|
||||
name="normal_login"
|
||||
initialValues={{
|
||||
|
||||
organization: application.organization,
|
||||
application: application.name,
|
||||
autoSignin: true,
|
||||
|
@ -420,7 +420,7 @@ export function getAuthUrl(application, provider, method, code) {
|
||||
} else if (provider.type === "AzureADB2C") {
|
||||
return `https://${provider.domain}.b2clogin.com/${provider.domain}.onmicrosoft.com/${provider.appId}/oauth2/v2.0/authorize?client_id=${provider.clientId}&nonce=defaultNonce&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${scope}&response_type=code&state=${state}&prompt=login`;
|
||||
} else if (provider.type === "DingTalk") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&prompt=login%20consent&state=${state}`;
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&prompt=consent&state=${state}`;
|
||||
} else if (provider.type === "WeChat") {
|
||||
if (navigator.userAgent.includes("MicroMessenger")) {
|
||||
return `${authInfo[provider.type].mpEndpoint}?appid=${provider.clientId2}&redirect_uri=${redirectUri}&state=${state}&scope=${authInfo[provider.type].mpScope}&response_type=code#wechat_redirect`;
|
||||
|
@ -14,12 +14,11 @@
|
||||
|
||||
import * as faceapi from "face-api.js";
|
||||
import React, {useState} from "react";
|
||||
import {Button, Modal, Progress, Space, Spin, message} from "antd";
|
||||
import {Button, Modal, Progress, Spin, message} from "antd";
|
||||
import i18next from "i18next";
|
||||
import Dragger from "antd/es/upload/Dragger";
|
||||
|
||||
const FaceRecognitionModal = (props) => {
|
||||
const {visible, onOk, onCancel, withImage} = props;
|
||||
const {visible, onOk, onCancel} = props;
|
||||
const [modelsLoaded, setModelsLoaded] = React.useState(false);
|
||||
const [isCameraCaptured, setIsCameraCaptured] = useState(false);
|
||||
|
||||
@ -29,10 +28,6 @@ const FaceRecognitionModal = (props) => {
|
||||
const mediaStreamRef = React.useRef(null);
|
||||
const [percent, setPercent] = useState(0);
|
||||
|
||||
const [files, setFiles] = useState([]);
|
||||
const [currentFaceId, setCurrentFaceId] = React.useState();
|
||||
const [currentFaceIndex, setCurrentFaceIndex] = React.useState();
|
||||
|
||||
React.useEffect(() => {
|
||||
const loadModels = async() => {
|
||||
// const MODEL_URL = process.env.PUBLIC_URL + "/models";
|
||||
@ -55,9 +50,6 @@ const FaceRecognitionModal = (props) => {
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (withImage) {
|
||||
return;
|
||||
}
|
||||
if (visible) {
|
||||
setPercent(0);
|
||||
if (modelsLoaded) {
|
||||
@ -83,9 +75,6 @@ const FaceRecognitionModal = (props) => {
|
||||
}, [visible, modelsLoaded]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (withImage) {
|
||||
return;
|
||||
}
|
||||
if (isCameraCaptured) {
|
||||
let count = 0;
|
||||
const interval = setInterval(() => {
|
||||
@ -109,9 +98,6 @@ const FaceRecognitionModal = (props) => {
|
||||
}, [isCameraCaptured]);
|
||||
|
||||
const handleStreamVideo = () => {
|
||||
if (withImage) {
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
let goodCount = 0;
|
||||
if (!detection.current) {
|
||||
@ -162,163 +148,73 @@ const FaceRecognitionModal = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getBase64 = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!withImage) {
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
destroyOnClose={true}
|
||||
open={visible && isCameraCaptured}
|
||||
title={i18next.t("login:Face Recognition")}
|
||||
width={350}
|
||||
footer={[
|
||||
<Button key="back" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Progress percent={percent} />
|
||||
<div style={{
|
||||
marginTop: "20px",
|
||||
marginBottom: "50px",
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
{
|
||||
modelsLoaded ?
|
||||
<div style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
onPlay={handleStreamVideo}
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
height: "220px",
|
||||
verticalAlign: "middle",
|
||||
width: "220px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
></video>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
width: "240px",
|
||||
height: "240px",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
}}>
|
||||
<svg width="240" height="240" fill="none">
|
||||
<circle
|
||||
strokeDasharray="700"
|
||||
strokeDashoffset={700 - 6.9115 * percent}
|
||||
strokeWidth="4"
|
||||
cx="120"
|
||||
cy="120"
|
||||
r="110"
|
||||
stroke="#5734d3"
|
||||
transform="rotate(-90, 120, 120)"
|
||||
strokeLinecap="round"
|
||||
style={{transition: "all .2s linear"}}
|
||||
></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<canvas ref={canvasRef} style={{position: "absolute"}} />
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<Spin tip={i18next.t("login:Loading")} size="large"
|
||||
style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
|
||||
<div className="content" />
|
||||
</Spin>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div>
|
||||
<Modal closable={false}
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
destroyOnClose={true}
|
||||
open={visible}
|
||||
open={visible && isCameraCaptured}
|
||||
title={i18next.t("login:Face Recognition")}
|
||||
width={350}
|
||||
footer={[
|
||||
<Button key="ok" type={"primary"} disabled={!currentFaceId || currentFaceId?.length === 0} onClick={() => {
|
||||
onOk(Array.from(currentFaceId.descriptor));
|
||||
}}>
|
||||
Ok
|
||||
</Button>,
|
||||
<Button key="back" onClick={onCancel}>
|
||||
Cancel
|
||||
Cancel
|
||||
</Button>,
|
||||
]}>
|
||||
<Space direction={"vertical"} style={{width: "100%"}}>
|
||||
<Dragger
|
||||
multiple={true}
|
||||
defaultFileList={files}
|
||||
style={{width: "100%"}}
|
||||
beforeUpload={(file) => {
|
||||
getBase64(file).then(res => {
|
||||
file.base64 = res;
|
||||
files.push(file);
|
||||
});
|
||||
setCurrentFaceId([]);
|
||||
return false;
|
||||
}}
|
||||
onRemove={(file) => {
|
||||
const index = files.indexOf(file);
|
||||
const newFileList = files.slice();
|
||||
newFileList.splice(index, 1);
|
||||
setFiles(newFileList);
|
||||
setCurrentFaceId([]);
|
||||
}}
|
||||
>
|
||||
<p>{i18next.t("general:Click to Upload")}</p>
|
||||
</Dragger >
|
||||
]}
|
||||
>
|
||||
<Progress percent={percent} />
|
||||
<div style={{marginTop: "20px", marginBottom: "50px", justifyContent: "center", alignContent: "center", position: "relative", flexDirection: "column"}}>
|
||||
{
|
||||
modelsLoaded ? <Button style={{width: "100%"}} onClick={async() => {
|
||||
let maxScore = 0;
|
||||
for (const file of files) {
|
||||
const fileIndex = files.indexOf(file);
|
||||
const img = new Image();
|
||||
img.src = file.base64;
|
||||
const faceIds = await faceapi.detectAllFaces(img, new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceDescriptors();
|
||||
if (faceIds[0]?.detection.score > 0.9 && faceIds[0]?.detection.score > maxScore) {
|
||||
maxScore = faceIds[0]?.detection.score;
|
||||
setCurrentFaceId(faceIds[0]);
|
||||
setCurrentFaceIndex(fileIndex);
|
||||
}
|
||||
}
|
||||
if (maxScore < 0.9) {
|
||||
message.error(i18next.t("login:Face recognition failed"));
|
||||
}
|
||||
}}> {i18next.t("application:Generate faceId")}</Button> : null
|
||||
modelsLoaded ?
|
||||
<div style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
onPlay={handleStreamVideo}
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
height: "220px",
|
||||
verticalAlign: "middle",
|
||||
width: "220px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
></video>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
width: "240px",
|
||||
height: "240px",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
}}>
|
||||
<svg width="240" height="240" fill="none">
|
||||
<circle
|
||||
strokeDasharray="700"
|
||||
strokeDashoffset={700 - 6.9115 * percent}
|
||||
strokeWidth="4"
|
||||
cx="120"
|
||||
cy="120"
|
||||
r="110"
|
||||
stroke="#5734d3"
|
||||
transform="rotate(-90, 120, 120)"
|
||||
strokeLinecap="round"
|
||||
style={{transition: "all .2s linear"}}
|
||||
></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<canvas ref={canvasRef} style={{position: "absolute"}} />
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<Spin tip={i18next.t("login:Loading")} size="large" style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
|
||||
<div className="content" />
|
||||
</Spin>
|
||||
</div>
|
||||
}
|
||||
</Space>
|
||||
{
|
||||
currentFaceId && currentFaceId.length !== 0 ? (
|
||||
<React.Fragment>
|
||||
<div>{i18next.t("application:Select")}:{files[currentFaceIndex]?.name}</div>
|
||||
<div><img src={files[currentFaceIndex]?.base64} alt="selected" style={{width: "100%"}} /></div>
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>;
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FaceRecognitionModal;
|
||||
|
@ -30,7 +30,6 @@ class LanguageSelect extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
languages: props.languages ?? Setting.Countries.map(item => item.key),
|
||||
onClick: props.onClick,
|
||||
};
|
||||
|
||||
Setting.Countries.forEach((country) => {
|
||||
@ -51,9 +50,6 @@ class LanguageSelect extends React.Component {
|
||||
render() {
|
||||
const languageItems = this.getOrganizationLanguages(this.state.languages);
|
||||
const onClick = (e) => {
|
||||
if (typeof this.state.onClick === "function") {
|
||||
this.state.onClick(e.key);
|
||||
}
|
||||
Setting.setLanguage(e.key);
|
||||
};
|
||||
|
||||
|
@ -97,16 +97,12 @@ class FaceIdTable extends React.Component {
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("user:Face IDs")}
|
||||
<Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true, withImage: false})}>
|
||||
<Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true})}>
|
||||
{i18next.t("general:Add Face Id")}
|
||||
</Button>
|
||||
<Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true, withImage: true})}>
|
||||
{i18next.t("general:Add Face Id with image")}
|
||||
</Button>
|
||||
<Suspense fallback={null}>
|
||||
<FaceRecognitionModal
|
||||
visible={this.state.openFaceRecognitionModal}
|
||||
withImage={this.state.withImage}
|
||||
onOk={(faceIdData) => {
|
||||
this.addFaceId(table, faceIdData);
|
||||
this.setState({openFaceRecognitionModal: false});
|
||||
|
Reference in New Issue
Block a user