mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-20 17:23:49 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
4fc7600865 | |||
19f62a461b | |||
7ddc2778c0 | |||
b96fa2a995 | |||
fcfb73af6e | |||
43bebc03b9 | |||
c5f25cbc7d | |||
3feb6ce84d | |||
08d6b45fc5 | |||
56d0de64dc | |||
1813e8e8c7 | |||
e27c764a55 | |||
e5a2057382 | |||
8457ff7433 | |||
888a6f2feb | |||
b57b64fc36 | |||
0d239ba1cf | |||
8927e08217 | |||
0636069584 |
@ -28,6 +28,7 @@ ldapServerPort = 389
|
|||||||
ldapsCertId = ""
|
ldapsCertId = ""
|
||||||
ldapsServerPort = 636
|
ldapsServerPort = 636
|
||||||
radiusServerPort = 1812
|
radiusServerPort = 1812
|
||||||
|
radiusDefaultOrganization = "built-in"
|
||||||
radiusSecret = "secret"
|
radiusSecret = "secret"
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -617,6 +618,17 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
|
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.EmailRegex != "" {
|
||||||
|
reg, err := regexp.Compile(provider.EmailRegex)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reg.MatchString(userInfo.Email) {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("check:Email is invalid")))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if authForm.Method == "signup" {
|
if authForm.Method == "signup" {
|
||||||
|
@ -353,13 +353,7 @@ func (c *ApiController) AddUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := object.GetUserCount("", "", "", "")
|
if err := checkQuotaForUser(); err != nil {
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkQuotaForUser(int(count)); err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -294,12 +294,18 @@ func checkQuotaForProvider(count int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkQuotaForUser(count int) error {
|
func checkQuotaForUser() error {
|
||||||
quota := conf.GetConfigQuota().User
|
quota := conf.GetConfigQuota().User
|
||||||
if quota == -1 {
|
if quota == -1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if count >= quota {
|
|
||||||
|
count, err := object.GetUserCount("", "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(count) >= quota {
|
||||||
return fmt.Errorf("user quota is exceeded")
|
return fmt.Errorf("user quota is exceeded")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -195,6 +195,12 @@ type GitHubUserEmailInfo struct {
|
|||||||
Visibility string `json:"visibility"`
|
Visibility string `json:"visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GitHubErrorInfo struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
DocumentationUrl string `json:"documentation_url"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -236,13 +242,23 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var userEmails []GitHubUserEmailInfo
|
if respEmail.StatusCode != 200 {
|
||||||
err = json.Unmarshal(emailBody, &userEmails)
|
var errMessage GitHubErrorInfo
|
||||||
if err != nil {
|
err = json.Unmarshal(emailBody, &errMessage)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
|
fmt.Printf("GithubIdProvider:GetUserInfo() error, status code = %d, error message = %v\n", respEmail.StatusCode, errMessage)
|
||||||
|
} else {
|
||||||
|
var userEmails []GitHubUserEmailInfo
|
||||||
|
err = json.Unmarshal(emailBody, &userEmails)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
|
161
idp/kwai.go
Normal file
161
idp/kwai.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2024 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.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KwaiIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKwaiIdProvider(clientId string, clientSecret string, redirectUrl string) *KwaiIdProvider {
|
||||||
|
idp := &KwaiIdProvider{}
|
||||||
|
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *KwaiIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *KwaiIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
endpoint := oauth2.Endpoint{
|
||||||
|
TokenURL: "https://open.kuaishou.com/oauth2/access_token",
|
||||||
|
AuthURL: "https://open.kuaishou.com/oauth2/authorize", // qr code: /oauth2/connect
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &oauth2.Config{
|
||||||
|
Scopes: []string{"user_info"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type KwaiTokenResp struct {
|
||||||
|
Result int `json:"result"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
Scopes []string `json:"scopes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
func (idp *KwaiIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"app_id": idp.Config.ClientID,
|
||||||
|
"app_secret": idp.Config.ClientSecret,
|
||||||
|
"code": code,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
}
|
||||||
|
tokenUrl := fmt.Sprintf("%s?app_id=%s&app_secret=%s&code=%s&grant_type=authorization_code",
|
||||||
|
idp.Config.Endpoint.TokenURL, params["app_id"], params["app_secret"], params["code"])
|
||||||
|
resp, err := idp.Client.Get(tokenUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var tokenResp KwaiTokenResp
|
||||||
|
err = json.Unmarshal(body, &tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tokenResp.Result != 1 {
|
||||||
|
return nil, fmt.Errorf("get token error: %s", tokenResp.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenResp.AccessToken,
|
||||||
|
RefreshToken: tokenResp.RefreshToken,
|
||||||
|
Expiry: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["open_id"] = tokenResp.OpenId
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// More details: https://open.kuaishou.com/openapi/user_info
|
||||||
|
type KwaiUserInfo struct {
|
||||||
|
Result int `json:"result"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
|
UserInfo struct {
|
||||||
|
Head string `json:"head"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sex string `json:"sex"`
|
||||||
|
City string `json:"city"`
|
||||||
|
} `json:"user_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
func (idp *KwaiIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
userInfoUrl := fmt.Sprintf("https://open.kuaishou.com/openapi/user_info?app_id=%s&access_token=%s",
|
||||||
|
idp.Config.ClientID, token.AccessToken)
|
||||||
|
|
||||||
|
resp, err := idp.Client.Get(userInfoUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kwaiUserInfo KwaiUserInfo
|
||||||
|
err = json.Unmarshal(body, &kwaiUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kwaiUserInfo.Result != 1 {
|
||||||
|
return nil, fmt.Errorf("get user info error: %s", kwaiUserInfo.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: token.Extra("open_id").(string),
|
||||||
|
Username: kwaiUserInfo.UserInfo.Name,
|
||||||
|
DisplayName: kwaiUserInfo.UserInfo.Name,
|
||||||
|
AvatarUrl: kwaiUserInfo.UserInfo.Head,
|
||||||
|
Extra: map[string]string{
|
||||||
|
"gender": kwaiUserInfo.UserInfo.Sex,
|
||||||
|
"city": kwaiUserInfo.UserInfo.City,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
@ -113,6 +113,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
|
|||||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Douyin":
|
case "Douyin":
|
||||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
|
case "Kwai":
|
||||||
|
return NewKwaiIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Bilibili":
|
case "Bilibili":
|
||||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "MetaMask":
|
case "MetaMask":
|
||||||
|
5
main.go
5
main.go
@ -83,6 +83,11 @@ func main() {
|
|||||||
// logs.SetLevel(logs.LevelInformational)
|
// logs.SetLevel(logs.LevelInformational)
|
||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
|
||||||
|
err = util.StopOldInstance(port)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
go ldap.StartLdapServer()
|
go ldap.StartLdapServer()
|
||||||
go radius.StartRadiusServer()
|
go radius.StartRadiusServer()
|
||||||
go object.ClearThroughputPerSecond()
|
go object.ClearThroughputPerSecond()
|
||||||
|
@ -19,288 +19,90 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dashboard struct {
|
type DashboardDateItem struct {
|
||||||
OrganizationCounts []int `json:"organizationCounts"`
|
CreatedTime string `json:"createTime"`
|
||||||
UserCounts []int `json:"userCounts"`
|
|
||||||
ProviderCounts []int `json:"providerCounts"`
|
|
||||||
ApplicationCounts []int `json:"applicationCounts"`
|
|
||||||
SubscriptionCounts []int `json:"subscriptionCounts"`
|
|
||||||
RoleCounts []int `json:"roleCounts"`
|
|
||||||
GroupCounts []int `json:"groupCounts"`
|
|
||||||
ResourceCounts []int `json:"resourceCounts"`
|
|
||||||
CertCounts []int `json:"certCounts"`
|
|
||||||
PermissionCounts []int `json:"permissionCounts"`
|
|
||||||
TransactionCounts []int `json:"transactionCounts"`
|
|
||||||
ModelCounts []int `json:"modelCounts"`
|
|
||||||
AdapterCounts []int `json:"adapterCounts"`
|
|
||||||
EnforcerCounts []int `json:"enforcerCounts"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboard(owner string) (*Dashboard, error) {
|
type DashboardMapItem struct {
|
||||||
|
dashboardDateItems []DashboardDateItem
|
||||||
|
itemCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDashboard(owner string) (*map[string][]int64, error) {
|
||||||
if owner == "All" {
|
if owner == "All" {
|
||||||
owner = ""
|
owner = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard := &Dashboard{
|
dashboard := make(map[string][]int64)
|
||||||
OrganizationCounts: make([]int, 31),
|
dashboardMap := sync.Map{}
|
||||||
UserCounts: make([]int, 31),
|
tableNames := []string{"organization", "user", "provider", "application", "subscription", "role", "group", "resource", "cert", "permission", "transaction", "model", "adapter", "enforcer"}
|
||||||
ProviderCounts: make([]int, 31),
|
|
||||||
ApplicationCounts: make([]int, 31),
|
time30day := time.Now().AddDate(0, 0, -30)
|
||||||
SubscriptionCounts: make([]int, 31),
|
var wg sync.WaitGroup
|
||||||
RoleCounts: make([]int, 31),
|
var err error
|
||||||
GroupCounts: make([]int, 31),
|
wg.Add(len(tableNames))
|
||||||
ResourceCounts: make([]int, 31),
|
ch := make(chan error, len(tableNames))
|
||||||
CertCounts: make([]int, 31),
|
for _, tableName := range tableNames {
|
||||||
PermissionCounts: make([]int, 31),
|
dashboard[tableName+"Counts"] = make([]int64, 31)
|
||||||
TransactionCounts: make([]int, 31),
|
tableName := tableName
|
||||||
ModelCounts: make([]int, 31),
|
go func(ch chan error) {
|
||||||
AdapterCounts: make([]int, 31),
|
defer wg.Done()
|
||||||
EnforcerCounts: make([]int, 31),
|
dashboardDateItems := []DashboardDateItem{}
|
||||||
|
var countResult int64
|
||||||
|
|
||||||
|
dbQueryBefore := ormer.Engine.Cols("created_time")
|
||||||
|
dbQueryAfter := ormer.Engine.Cols("created_time")
|
||||||
|
|
||||||
|
if owner != "" {
|
||||||
|
dbQueryAfter = dbQueryAfter.And("owner = ?", owner)
|
||||||
|
dbQueryBefore = dbQueryBefore.And("owner = ?", owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
if countResult, err = dbQueryBefore.And("created_time < ?", time30day).Table(tableName).Count(); err != nil {
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = dbQueryAfter.And("created_time >= ?", time30day).Table(tableName).Find(&dashboardDateItems); err != nil {
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardMap.Store(tableName, DashboardMapItem{
|
||||||
|
dashboardDateItems: dashboardDateItems,
|
||||||
|
itemCount: countResult,
|
||||||
|
})
|
||||||
|
}(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
organizations := []Organization{}
|
|
||||||
users := []User{}
|
|
||||||
providers := []Provider{}
|
|
||||||
applications := []Application{}
|
|
||||||
subscriptions := []Subscription{}
|
|
||||||
roles := []Role{}
|
|
||||||
groups := []Group{}
|
|
||||||
resources := []Resource{}
|
|
||||||
certs := []Cert{}
|
|
||||||
permissions := []Permission{}
|
|
||||||
transactions := []Transaction{}
|
|
||||||
models := []Model{}
|
|
||||||
adapters := []Adapter{}
|
|
||||||
enforcers := []Enforcer{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(14)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&roles, &Role{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&groups, &Group{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&resources, &Resource{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&certs, &Cert{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&permissions, &Permission{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&transactions, &Transaction{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&models, &Model{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&adapters, &Adapter{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&enforcers, &Enforcer{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
|
||||||
|
for err = range ch {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
for i := 30; i >= 0; i-- {
|
for i := 30; i >= 0; i-- {
|
||||||
cutTime := nowTime.AddDate(0, 0, -i)
|
cutTime := nowTime.AddDate(0, 0, -i)
|
||||||
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime)
|
for _, tableName := range tableNames {
|
||||||
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime)
|
item, exist := dashboardMap.Load(tableName)
|
||||||
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
|
if !exist {
|
||||||
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
|
continue
|
||||||
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
|
}
|
||||||
dashboard.RoleCounts[30-i] = countCreatedBefore(roles, cutTime)
|
dashboard[tableName+"Counts"][30-i] = countCreatedBefore(item.(DashboardMapItem), cutTime)
|
||||||
dashboard.GroupCounts[30-i] = countCreatedBefore(groups, cutTime)
|
}
|
||||||
dashboard.ResourceCounts[30-i] = countCreatedBefore(resources, cutTime)
|
|
||||||
dashboard.CertCounts[30-i] = countCreatedBefore(certs, cutTime)
|
|
||||||
dashboard.PermissionCounts[30-i] = countCreatedBefore(permissions, cutTime)
|
|
||||||
dashboard.TransactionCounts[30-i] = countCreatedBefore(transactions, cutTime)
|
|
||||||
dashboard.ModelCounts[30-i] = countCreatedBefore(models, cutTime)
|
|
||||||
dashboard.AdapterCounts[30-i] = countCreatedBefore(adapters, cutTime)
|
|
||||||
dashboard.EnforcerCounts[30-i] = countCreatedBefore(enforcers, cutTime)
|
|
||||||
}
|
}
|
||||||
return dashboard, nil
|
return &dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func countCreatedBefore(objects interface{}, before time.Time) int {
|
func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 {
|
||||||
count := 0
|
count := dashboardMapItem.itemCount
|
||||||
switch obj := objects.(type) {
|
for _, e := range dashboardMapItem.dashboardDateItems {
|
||||||
case []Organization:
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
|
||||||
for _, o := range obj {
|
if createdTime.Before(before) {
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime)
|
count++
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []User:
|
|
||||||
for _, u := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Provider:
|
|
||||||
for _, p := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Application:
|
|
||||||
for _, a := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Subscription:
|
|
||||||
for _, s := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Role:
|
|
||||||
for _, r := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Group:
|
|
||||||
for _, g := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", g.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Resource:
|
|
||||||
for _, r := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Cert:
|
|
||||||
for _, c := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", c.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Permission:
|
|
||||||
for _, p := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Transaction:
|
|
||||||
for _, t := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", t.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Model:
|
|
||||||
for _, m := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", m.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Adapter:
|
|
||||||
for _, a := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Enforcer:
|
|
||||||
for _, e := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
|
@ -69,8 +69,8 @@ type Organization struct {
|
|||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(200)" json:"masterPassword"`
|
||||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
DefaultPassword string `xorm:"varchar(200)" json:"defaultPassword"`
|
||||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
InitScore int `json:"initScore"`
|
InitScore int `json:"initScore"`
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
@ -70,6 +71,7 @@ type Provider struct {
|
|||||||
IdP string `xorm:"mediumtext" json:"idP"`
|
IdP string `xorm:"mediumtext" json:"idP"`
|
||||||
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
|
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
|
||||||
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
|
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
|
||||||
|
EmailRegex string `xorm:"varchar(200)" json:"emailRegex"`
|
||||||
|
|
||||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||||
}
|
}
|
||||||
@ -200,6 +202,13 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.EmailRegex != "" {
|
||||||
|
_, err := regexp.Compile(provider.EmailRegex)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if name != provider.Name {
|
if name != provider.Name {
|
||||||
err := providerChangeTrigger(name, provider.Name)
|
err := providerChangeTrigger(name, provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -234,6 +243,13 @@ func AddProvider(provider *Provider) (bool, error) {
|
|||||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.EmailRegex != "" {
|
||||||
|
_, err := regexp.Compile(provider.EmailRegex)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(provider)
|
affected, err := ormer.Engine.Insert(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -338,6 +338,10 @@ func roleChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
for j, u := range role.Roles {
|
for j, u := range role.Roles {
|
||||||
|
if u == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
owner, name := util.GetOwnerAndNameFromId(u)
|
owner, name := util.GetOwnerAndNameFromId(u)
|
||||||
if name == oldName {
|
if name == oldName {
|
||||||
role.Roles[j] = util.GetId(owner, newName)
|
role.Roles[j] = util.GetId(owner, newName)
|
||||||
@ -358,6 +362,10 @@ func roleChangeTrigger(oldName string, newName string) error {
|
|||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
for j, u := range permission.Roles {
|
for j, u := range permission.Roles {
|
||||||
// u = organization/username
|
// u = organization/username
|
||||||
|
if u == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
owner, name := util.GetOwnerAndNameFromId(u)
|
owner, name := util.GetOwnerAndNameFromId(u)
|
||||||
if name == oldName {
|
if name == oldName {
|
||||||
permission.Roles[j] = util.GetId(owner, newName)
|
permission.Roles[j] = util.GetId(owner, newName)
|
||||||
|
@ -338,6 +338,9 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
||||||
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
|
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
|
||||||
}
|
}
|
||||||
|
if authnRequest.ProtocolBinding == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" {
|
||||||
|
method = "POST"
|
||||||
|
}
|
||||||
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
|
@ -309,22 +309,29 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oldTokenScope string
|
||||||
if application.TokenFormat == "JWT-Standard" {
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
_, err = ParseStandardJwtToken(refreshToken, cert)
|
oldToken, err := ParseStandardJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
oldTokenScope = oldToken.Scope
|
||||||
} else {
|
} else {
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
oldToken, err := ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
oldTokenScope = oldToken.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope == "" {
|
||||||
|
scope = oldTokenScope
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new token
|
// generate a new token
|
||||||
|
@ -129,6 +129,7 @@ type User struct {
|
|||||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||||
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||||
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
||||||
|
Kwai string `xorm:"kwai varchar(100)" json:"kwai"`
|
||||||
Line string `xorm:"line varchar(100)" json:"line"`
|
Line string `xorm:"line varchar(100)" json:"line"`
|
||||||
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
|
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
|
||||||
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
|
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
|
||||||
@ -237,6 +238,7 @@ type MfaAccount struct {
|
|||||||
AccountName string `xorm:"varchar(100)" json:"accountName"`
|
AccountName string `xorm:"varchar(100)" json:"accountName"`
|
||||||
Issuer string `xorm:"varchar(100)" json:"issuer"`
|
Issuer string `xorm:"varchar(100)" json:"issuer"`
|
||||||
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
||||||
|
Origin string `xorm:"varchar(100)" json:"origin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FaceId struct {
|
type FaceId struct {
|
||||||
@ -697,7 +699,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||||
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon",
|
||||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||||
@ -844,11 +846,14 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := GetUserCount(user.Owner, "", "", "")
|
rankingItem := GetAccountItemByName("Ranking", organization)
|
||||||
if err != nil {
|
if rankingItem != nil {
|
||||||
return false, err
|
count, err := GetUserCount(user.Owner, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
user.Ranking = int(count + 1)
|
||||||
}
|
}
|
||||||
user.Ranking = int(count + 1)
|
|
||||||
|
|
||||||
if user.Groups != nil && len(user.Groups) > 0 {
|
if user.Groups != nil && len(user.Groups) > 0 {
|
||||||
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||||
|
@ -68,8 +68,10 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
|||||||
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||||
|
|
||||||
if organization == "" {
|
if organization == "" {
|
||||||
w.Write(r.Response(radius.CodeAccessReject))
|
organization = conf.GetConfigString("radiusDefaultOrganization")
|
||||||
return
|
if organization == "" {
|
||||||
|
organization = "built-in"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
|
@ -7558,6 +7558,9 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
"kwai": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -4981,6 +4981,8 @@ definitions:
|
|||||||
karma:
|
karma:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
kwai:
|
||||||
|
type: string
|
||||||
language:
|
language:
|
||||||
type: string
|
type: string
|
||||||
lark:
|
lark:
|
||||||
|
97
util/process.go
Normal file
97
util/process.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPidByPort(port int) (int, error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
cmd = exec.Command("cmd", "/c", "netstat -ano | findstr :"+strconv.Itoa(port))
|
||||||
|
case "darwin", "linux":
|
||||||
|
cmd = exec.Command("lsof", "-t", "-i", ":"+strconv.Itoa(port))
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exitErr.ExitCode() == 1 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if fields[1] == "0.0.0.0:"+strconv.Itoa(port) {
|
||||||
|
pid, err := strconv.Atoi(fields[len(fields)-1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pid, err := strconv.Atoi(fields[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopOldInstance(port int) error {
|
||||||
|
pid, err := getPidByPort(port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pid == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
process, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Printf("The old instance with pid: %d has been stopped\n", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
@ -55,47 +56,42 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
webpack: {
|
webpack: {
|
||||||
configure: {
|
configure: (webpackConfig, { env, paths }) => {
|
||||||
|
paths.appBuild = path.resolve(__dirname, "build-temp");
|
||||||
|
webpackConfig.output.path = path.resolve(__dirname, "build-temp");
|
||||||
|
|
||||||
// ignore webpack warnings by source-map-loader
|
// ignore webpack warnings by source-map-loader
|
||||||
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
|
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
|
||||||
ignoreWarnings: [
|
webpackConfig.ignoreWarnings = [
|
||||||
function ignoreSourcemapsloaderWarnings(warning) {
|
function ignoreSourcemapsloaderWarnings(warning) {
|
||||||
return (
|
return (
|
||||||
warning.module &&
|
warning.module &&
|
||||||
warning.module.resource.includes('node_modules') &&
|
warning.module.resource.includes("node_modules") &&
|
||||||
warning.details &&
|
warning.details &&
|
||||||
warning.details.includes('source-map-loader')
|
warning.details.includes("source-map-loader")
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
|
|
||||||
// use polyfill Buffer with Webpack 5
|
// use polyfill Buffer with Webpack 5
|
||||||
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
|
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
|
||||||
// https://craco.js.org/docs/configuration/webpack/
|
// https://craco.js.org/docs/configuration/webpack/
|
||||||
resolve: {
|
webpackConfig.resolve.fallback = {
|
||||||
fallback: {
|
buffer: require.resolve("buffer/"),
|
||||||
// "process": require.resolve('process/browser'),
|
process: false,
|
||||||
// "util": require.resolve("util/"),
|
util: false,
|
||||||
// "url": require.resolve("url/"),
|
url: false,
|
||||||
// "zlib": require.resolve("browserify-zlib"),
|
zlib: false,
|
||||||
// "stream": require.resolve("stream-browserify"),
|
stream: false,
|
||||||
// "http": require.resolve("stream-http"),
|
http: false,
|
||||||
// "https": require.resolve("https-browserify"),
|
https: false,
|
||||||
// "assert": require.resolve("assert/"),
|
assert: false,
|
||||||
"buffer": require.resolve('buffer/'),
|
crypto: false,
|
||||||
"process": false,
|
os: false,
|
||||||
"util": false,
|
fs: false,
|
||||||
"url": false,
|
};
|
||||||
"zlib": false,
|
|
||||||
"stream": false,
|
return webpackConfig;
|
||||||
"http": false,
|
|
||||||
"https": false,
|
|
||||||
"assert": false,
|
|
||||||
"buffer": false,
|
|
||||||
"crypto": false,
|
|
||||||
"os": false,
|
|
||||||
"fs": false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
21
web/mv.js
Normal file
21
web/mv.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const sourceDir = path.join(__dirname, "build-temp");
|
||||||
|
const targetDir = path.join(__dirname, "build");
|
||||||
|
|
||||||
|
if (!fs.existsSync(sourceDir)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Source directory "${sourceDir}" does not exist.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(targetDir)) {
|
||||||
|
fs.rmSync(targetDir, {recursive: true, force: true});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Target directory "${targetDir}" has been deleted successfully.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.renameSync(sourceDir, targetDir);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`);
|
@ -57,6 +57,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env PORT=7001 craco start",
|
"start": "cross-env PORT=7001 craco start",
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
|
"postbuild": "node mv.js",
|
||||||
"test": "craco test",
|
"test": "craco test",
|
||||||
"eject": "craco eject",
|
"eject": "craco eject",
|
||||||
"crowdin:sync": "crowdin upload && crowdin download",
|
"crowdin:sync": "crowdin upload && crowdin download",
|
||||||
|
@ -106,6 +106,22 @@ class InvitationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copySignupLink() {
|
||||||
|
let defaultApplication;
|
||||||
|
if (this.state.invitation.owner === "built-in") {
|
||||||
|
defaultApplication = "app-built-in";
|
||||||
|
} else {
|
||||||
|
const selectedOrganization = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner);
|
||||||
|
defaultApplication = selectedOrganization.defaultApplication;
|
||||||
|
if (!defaultApplication) {
|
||||||
|
Setting.showMessage("error", i18next.t("invitation:You need to specify a default application for ") + selectedOrganization.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
|
||||||
|
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||||
|
}
|
||||||
|
|
||||||
renderInvitation() {
|
renderInvitation() {
|
||||||
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
|
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
|
||||||
return (
|
return (
|
||||||
@ -114,16 +130,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}
|
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}
|
||||||
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} onClick={() => {
|
<Button style={{marginLeft: "20px"}} onClick={_ => this.copySignupLink()}>
|
||||||
let defaultApplication;
|
|
||||||
if (this.state.invitation.owner === "built-in") {
|
|
||||||
defaultApplication = "app-built-in";
|
|
||||||
} else {
|
|
||||||
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
|
|
||||||
}
|
|
||||||
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
|
|
||||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
|
||||||
}}>
|
|
||||||
{i18next.t("application:Copy signup page URL")}
|
{i18next.t("application:Copy signup page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
@ -330,16 +337,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} size="large" onClick={() => {
|
<Button style={{marginLeft: "20px"}} size="large" onClick={_ => this.copySignupLink()}>
|
||||||
let defaultApplication;
|
|
||||||
if (this.state.invitation.owner === "built-in") {
|
|
||||||
defaultApplication = "app-built-in";
|
|
||||||
} else {
|
|
||||||
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
|
|
||||||
}
|
|
||||||
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
|
|
||||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
|
||||||
}}>
|
|
||||||
{i18next.t("application:Copy signup page URL")}
|
{i18next.t("application:Copy signup page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
@ -633,6 +633,20 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
this.state.provider.category === "OAuth" ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={4} value={this.state.provider.emailRegex} onChange={e => {
|
||||||
|
this.updateProviderField("emailRegex", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type === "Custom" ? (
|
this.state.provider.type === "Custom" ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -985,6 +985,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Bilibili", name: "Bilibili"},
|
{id: "Bilibili", name: "Bilibili"},
|
||||||
{id: "Okta", name: "Okta"},
|
{id: "Okta", name: "Okta"},
|
||||||
{id: "Douyin", name: "Douyin"},
|
{id: "Douyin", name: "Douyin"},
|
||||||
|
{id: "Kwai", name: "Kwai"},
|
||||||
{id: "Line", name: "Line"},
|
{id: "Line", name: "Line"},
|
||||||
{id: "Amazon", name: "Amazon"},
|
{id: "Amazon", name: "Amazon"},
|
||||||
{id: "Auth0", name: "Auth0"},
|
{id: "Auth0", name: "Auth0"},
|
||||||
|
@ -204,7 +204,7 @@ class AuthCallback extends React.Component {
|
|||||||
}
|
}
|
||||||
const SAMLResponse = res.data;
|
const SAMLResponse = res.data;
|
||||||
const redirectUri = res.data2.redirectUrl;
|
const redirectUri = res.data2.redirectUrl;
|
||||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
31
web/src/auth/KwaiLoginButton.js
Normal file
31
web/src/auth/KwaiLoginButton.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2024 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 {createButton} from "react-social-login-buttons";
|
||||||
|
import {StaticBaseUrl} from "../Setting";
|
||||||
|
|
||||||
|
function Icon({width = 24, height = 24}) {
|
||||||
|
return <img src={`${StaticBaseUrl}/buttons/kwai.svg`} alt="Sign in with Kwai" style={{width: width, height: height}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
text: "Sign in with Kwai",
|
||||||
|
icon: Icon,
|
||||||
|
style: {background: "#ffffff", color: "#000000"},
|
||||||
|
activeStyle: {background: "#ededee"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const KwaiLoginButton = createButton(config);
|
||||||
|
|
||||||
|
export default KwaiLoginButton;
|
@ -505,7 +505,7 @@ class LoginPage extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
const SAMLResponse = res.data;
|
const SAMLResponse = res.data;
|
||||||
const redirectUri = res.data2.redirectUrl;
|
const redirectUri = res.data2.redirectUrl;
|
||||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -119,6 +119,10 @@ const authInfo = {
|
|||||||
scope: "user_info",
|
scope: "user_info",
|
||||||
endpoint: "https://open.douyin.com/platform/oauth/connect",
|
endpoint: "https://open.douyin.com/platform/oauth/connect",
|
||||||
},
|
},
|
||||||
|
Kwai: {
|
||||||
|
scope: "user_info",
|
||||||
|
endpoint: "https://open.kuaishou.com/oauth2/connect",
|
||||||
|
},
|
||||||
Custom: {
|
Custom: {
|
||||||
endpoint: "https://example.com/",
|
endpoint: "https://example.com/",
|
||||||
},
|
},
|
||||||
@ -470,6 +474,8 @@ export function getAuthUrl(application, provider, method, code) {
|
|||||||
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
|
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
|
||||||
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
|
} else if (provider.type === "Kwai") {
|
||||||
|
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Custom") {
|
} else if (provider.type === "Custom") {
|
||||||
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
|
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
|
||||||
} else if (provider.type === "Bilibili") {
|
} else if (provider.type === "Bilibili") {
|
||||||
|
@ -40,6 +40,7 @@ import SteamLoginButton from "./SteamLoginButton";
|
|||||||
import BilibiliLoginButton from "./BilibiliLoginButton";
|
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||||
import OktaLoginButton from "./OktaLoginButton";
|
import OktaLoginButton from "./OktaLoginButton";
|
||||||
import DouyinLoginButton from "./DouyinLoginButton";
|
import DouyinLoginButton from "./DouyinLoginButton";
|
||||||
|
import KwaiLoginButton from "./KwaiLoginButton";
|
||||||
import LoginButton from "./LoginButton";
|
import LoginButton from "./LoginButton";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import {WechatOfficialAccountModal} from "./Util";
|
import {WechatOfficialAccountModal} from "./Util";
|
||||||
@ -96,6 +97,8 @@ function getSigninButton(provider) {
|
|||||||
return <OktaLoginButton text={text} align={"center"} />;
|
return <OktaLoginButton text={text} align={"center"} />;
|
||||||
} else if (provider.type === "Douyin") {
|
} else if (provider.type === "Douyin") {
|
||||||
return <DouyinLoginButton text={text} align={"center"} />;
|
return <DouyinLoginButton text={text} align={"center"} />;
|
||||||
|
} else if (provider.type === "Kwai") {
|
||||||
|
return <KwaiLoginButton text={text} align={"center"} />;
|
||||||
} else {
|
} else {
|
||||||
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,18 @@ class MfaAccountTable extends React.Component {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("mfaAccount:Origin"),
|
||||||
|
dataIndex: "origin",
|
||||||
|
key: "origin",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Input value={text} onChange={e => {
|
||||||
|
this.updateField(table, index, "origin", e.target.value);
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("mfaAccount:Secret Key"),
|
title: i18next.t("mfaAccount:Secret Key"),
|
||||||
dataIndex: "secretKey",
|
dataIndex: "secretKey",
|
||||||
|
Reference in New Issue
Block a user