mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 19:53:49 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
2b89f6b37b | |||
c699e35e6b | |||
e28d90d0aa | |||
4fc7600865 | |||
19f62a461b | |||
7ddc2778c0 | |||
b96fa2a995 | |||
fcfb73af6e | |||
43bebc03b9 | |||
c5f25cbc7d | |||
3feb6ce84d |
@ -353,13 +353,7 @@ func (c *ApiController) AddUser() {
|
||||
return
|
||||
}
|
||||
|
||||
count, err := object.GetUserCount("", "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := checkQuotaForUser(int(count)); err != nil {
|
||||
if err := checkQuotaForUser(); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -294,12 +294,18 @@ func checkQuotaForProvider(count int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkQuotaForUser(count int) error {
|
||||
func checkQuotaForUser() error {
|
||||
quota := conf.GetConfigQuota().User
|
||||
if quota == -1 {
|
||||
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 nil
|
||||
|
2
go.mod
2
go.mod
@ -12,7 +12,7 @@ require (
|
||||
github.com/casdoor/go-sms-sender v0.25.0
|
||||
github.com/casdoor/gomail/v2 v2.0.1
|
||||
github.com/casdoor/ldapserver v1.2.0
|
||||
github.com/casdoor/notify v0.45.0
|
||||
github.com/casdoor/notify v1.0.0
|
||||
github.com/casdoor/oss v1.8.0
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||
github.com/casvisor/casvisor-go-sdk v1.4.0
|
||||
|
4
go.sum
4
go.sum
@ -1093,8 +1093,8 @@ github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR
|
||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
|
||||
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
|
||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||
github.com/casdoor/notify v1.0.0 h1:oldsaaQFPrlufm/OA314z8DwFVE1Tc9Gt1z4ptRHhXw=
|
||||
github.com/casdoor/notify v1.0.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
||||
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||
|
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
|
||||
case "Douyin":
|
||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Kwai":
|
||||
return NewKwaiIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Bilibili":
|
||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "MetaMask":
|
||||
|
29
notification/cucloud.go
Normal file
29
notification/cucloud.go
Normal file
@ -0,0 +1,29 @@
|
||||
// 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 notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/cucloud"
|
||||
)
|
||||
|
||||
func NewCucloudProvider(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType string) (notify.Notifier, error) {
|
||||
cucloud := cucloud.New(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(cucloud)
|
||||
|
||||
return notifier, nil
|
||||
}
|
@ -16,7 +16,7 @@ package notification
|
||||
|
||||
import "github.com/casdoor/notify"
|
||||
|
||||
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
|
||||
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string, regionId string) (notify.Notifier, error) {
|
||||
if typ == "Telegram" {
|
||||
return NewTelegramProvider(clientSecret, receiver)
|
||||
} else if typ == "Custom HTTP" {
|
||||
@ -53,6 +53,8 @@ func GetNotificationProvider(typ string, clientId string, clientSecret string, c
|
||||
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
|
||||
} else if typ == "Viber" {
|
||||
return NewViberProvider(clientId, clientSecret, appId, receiver)
|
||||
} else if typ == "CUCloud" {
|
||||
return NewCucloudProvider(clientId, clientSecret, appId, title, regionId, clientId2, metaData)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
|
||||
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
||||
var client notify.Notifier
|
||||
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata)
|
||||
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata, provider.RegionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -69,8 +69,8 @@ type Organization struct {
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||
MasterPassword string `xorm:"varchar(200)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(200)" json:"defaultPassword"`
|
||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||
InitScore int `json:"initScore"`
|
||||
|
@ -338,6 +338,10 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Roles {
|
||||
if u == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Roles[j] = util.GetId(owner, newName)
|
||||
@ -358,6 +362,10 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
if u == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Roles[j] = util.GetId(owner, newName)
|
||||
|
@ -338,6 +338,9 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
||||
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)
|
||||
|
||||
|
@ -129,6 +129,7 @@ type User struct {
|
||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
||||
Kwai string `xorm:"kwai varchar(100)" json:"kwai"`
|
||||
Line string `xorm:"line varchar(100)" json:"line"`
|
||||
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
|
||||
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
|
||||
@ -698,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",
|
||||
"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",
|
||||
"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",
|
||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||
@ -845,11 +846,14 @@ func AddUser(user *User) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
count, err := GetUserCount(user.Owner, "", "", "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
rankingItem := GetAccountItemByName("Ranking", organization)
|
||||
if rankingItem != nil {
|
||||
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 {
|
||||
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||
|
@ -133,6 +133,11 @@ func StaticFilter(ctx *context.Context) {
|
||||
path += urlPath
|
||||
}
|
||||
|
||||
err := appendThemeCookie(ctx, urlPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if strings.Contains(path, "/../") || !util.FileExist(path) {
|
||||
path = webBuildFolder + "/index.html"
|
||||
}
|
||||
|
99
routers/theme_filter.go
Normal file
99
routers/theme_filter.go
Normal file
@ -0,0 +1,99 @@
|
||||
// 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 routers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func appendThemeCookie(ctx *context.Context, urlPath string) error {
|
||||
if urlPath == "/login" {
|
||||
application, err := object.GetDefaultApplication(fmt.Sprintf("admin/built-in"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if application.ThemeData != nil {
|
||||
return setThemeDataCookie(ctx, application.ThemeData)
|
||||
}
|
||||
organization := application.OrganizationObj
|
||||
if organization == nil {
|
||||
organization, err = object.GetOrganization(fmt.Sprintf("admin/built-in"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if organization != nil {
|
||||
return setThemeDataCookie(ctx, organization.ThemeData)
|
||||
}
|
||||
} else if strings.HasPrefix(urlPath, "/login/oauth/authorize") {
|
||||
clientId := ctx.Input.Query("client_id")
|
||||
if clientId == "" {
|
||||
return nil
|
||||
}
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if application != nil {
|
||||
organization, err := object.GetOrganization(fmt.Sprintf("admin/%s", application.Organization))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if application.ThemeData != nil {
|
||||
return setThemeDataCookie(ctx, application.ThemeData)
|
||||
}
|
||||
if organization != nil {
|
||||
return setThemeDataCookie(ctx, organization.ThemeData)
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(urlPath, "/login/") {
|
||||
owner := strings.Replace(urlPath, "/login/", "", -1)
|
||||
if owner != "undefined" && owner != "oauth/undefined" {
|
||||
application, err := object.GetDefaultApplication(fmt.Sprintf("admin/%s", owner))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if application.ThemeData != nil {
|
||||
return setThemeDataCookie(ctx, application.ThemeData)
|
||||
}
|
||||
organization := application.OrganizationObj
|
||||
if organization == nil {
|
||||
organization, err = object.GetOrganization(fmt.Sprintf("admin/%s", owner))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if organization != nil {
|
||||
return setThemeDataCookie(ctx, organization.ThemeData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setThemeDataCookie(ctx *context.Context, themeData *object.ThemeData) error {
|
||||
themeDataString, err := json.Marshal(themeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.SetCookie("organizationTheme", string(themeDataString))
|
||||
return nil
|
||||
}
|
@ -7558,6 +7558,9 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"kwai": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -4981,6 +4981,8 @@ definitions:
|
||||
karma:
|
||||
type: integer
|
||||
format: int64
|
||||
kwai:
|
||||
type: string
|
||||
language:
|
||||
type: string
|
||||
lark:
|
||||
|
@ -29,7 +29,7 @@ func getPidByPort(port int) (int, error) {
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", "netstat -ano | findstr :"+strconv.Itoa(port))
|
||||
case "darwin", "linux":
|
||||
cmd = exec.Command("lsof", "-i", ":"+strconv.Itoa(port))
|
||||
cmd = exec.Command("lsof", "-t", "-i", ":"+strconv.Itoa(port))
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
@ -59,7 +59,7 @@ func getPidByPort(port int) (int, error) {
|
||||
return pid, nil
|
||||
}
|
||||
} else {
|
||||
pid, err := strconv.Atoi(fields[1])
|
||||
pid, err := strconv.Atoi(fields[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ const {Footer, Content} = Layout;
|
||||
|
||||
import {setTwoToneColor} from "@ant-design/icons";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as Cookie from "cookie";
|
||||
|
||||
setTwoToneColor("rgb(87,52,211)");
|
||||
|
||||
@ -360,11 +361,19 @@ class App extends Component {
|
||||
|
||||
renderPage() {
|
||||
if (this.isDoorPages()) {
|
||||
let themeData = this.state.themeData;
|
||||
if (this.state.organization === undefined) {
|
||||
const curCookie = Cookie.parse(document.cookie);
|
||||
if (curCookie["organizationTheme"] && curCookie["organizationTheme"] !== "null") {
|
||||
themeData = JSON.parse(curCookie["organizationTheme"]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={{
|
||||
token: {
|
||||
colorPrimary: this.state.themeData.colorPrimary,
|
||||
borderRadius: this.state.themeData.borderRadius,
|
||||
colorPrimary: themeData.colorPrimary,
|
||||
borderRadius: themeData.borderRadius,
|
||||
},
|
||||
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||
}}>
|
||||
|
@ -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() {
|
||||
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
|
||||
return (
|
||||
@ -114,16 +130,7 @@ class InvitationEditPage extends React.Component {
|
||||
{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 style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} onClick={() => {
|
||||
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"));
|
||||
}}>
|
||||
<Button style={{marginLeft: "20px"}} onClick={_ => this.copySignupLink()}>
|
||||
{i18next.t("application:Copy signup page URL")}
|
||||
</Button>
|
||||
{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"}}>
|
||||
<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"}} size="large" onClick={() => {
|
||||
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"));
|
||||
}}>
|
||||
<Button style={{marginLeft: "20px"}} size="large" onClick={_ => this.copySignupLink()}>
|
||||
{i18next.t("application:Copy signup page URL")}
|
||||
</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
|
@ -297,6 +297,8 @@ class ProviderEditPage extends React.Component {
|
||||
return Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"));
|
||||
} else if (provider.type === "WeChat Pay") {
|
||||
return Setting.getLabel(i18next.t("provider:App ID"), i18next.t("provider:App ID - Tooltip"));
|
||||
} else if (provider.type === "CUCloud") {
|
||||
return Setting.getLabel(i18next.t("provider:Account ID"), i18next.t("provider:Account ID - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"));
|
||||
}
|
||||
@ -393,6 +395,9 @@ class ProviderEditPage extends React.Component {
|
||||
} else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
|
||||
text = i18next.t("provider:App Key");
|
||||
tooltip = i18next.t("provider:App Key - Tooltip");
|
||||
} else if (provider.type === "CUCloud") {
|
||||
text = i18next.t("provider:Topic name");
|
||||
tooltip = i18next.t("provider:Topic name - Tooltip");
|
||||
}
|
||||
}
|
||||
|
||||
@ -771,7 +776,7 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
|
||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "CUCloud" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
@ -784,7 +789,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
|
||||
(this.state.provider.type === "WeChat Pay" || this.state.provider.type === "CUCloud") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecret2Label(this.state.provider)} :
|
||||
@ -870,9 +875,9 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? (
|
||||
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email", "CUCloud"].includes(this.state.provider.type) ? (
|
||||
<div>
|
||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||
{["Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||
@ -884,7 +889,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||
@ -896,7 +901,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{["Casdoor"].includes(this.state.provider.type) ?
|
||||
@ -910,7 +915,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "CUCloud"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||
@ -922,7 +927,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
@ -946,7 +951,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO"].includes(this.state.provider.type) ? (
|
||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO", "CUCloud"].includes(this.state.provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{["Casdoor"].includes(this.state.provider.type) ?
|
||||
@ -985,7 +990,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["Custom HTTP"].includes(this.state.provider.type) ? (
|
||||
{["Custom HTTP", "CUCloud"].includes(this.state.provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
||||
@ -997,7 +1002,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["Google Chat"].includes(this.state.provider.type) ? (
|
||||
{["Google Chat", "CUCloud"].includes(this.state.provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
|
||||
|
@ -405,6 +405,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/social_viber.png`,
|
||||
url: "https://www.viber.com/",
|
||||
},
|
||||
"CUCloud": {
|
||||
logo: `${StaticBaseUrl}/img/cucloud.png`,
|
||||
url: "https://www.cucloud.cn/",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -985,6 +989,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Bilibili", name: "Bilibili"},
|
||||
{id: "Okta", name: "Okta"},
|
||||
{id: "Douyin", name: "Douyin"},
|
||||
{id: "Kwai", name: "Kwai"},
|
||||
{id: "Line", name: "Line"},
|
||||
{id: "Amazon", name: "Amazon"},
|
||||
{id: "Auth0", name: "Auth0"},
|
||||
@ -1136,6 +1141,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Reddit", name: "Reddit"},
|
||||
{id: "Rocket Chat", name: "Rocket Chat"},
|
||||
{id: "Viber", name: "Viber"},
|
||||
{id: "CUCloud", name: "CUCloud"},
|
||||
]);
|
||||
} else {
|
||||
return [];
|
||||
|
@ -204,7 +204,7 @@ class AuthCallback extends React.Component {
|
||||
}
|
||||
const SAMLResponse = res.data;
|
||||
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 {
|
||||
|
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 {
|
||||
const SAMLResponse = res.data;
|
||||
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",
|
||||
endpoint: "https://open.douyin.com/platform/oauth/connect",
|
||||
},
|
||||
Kwai: {
|
||||
scope: "user_info",
|
||||
endpoint: "https://open.kuaishou.com/oauth2/connect",
|
||||
},
|
||||
Custom: {
|
||||
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}`;
|
||||
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
|
||||
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") {
|
||||
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "Bilibili") {
|
||||
|
@ -40,6 +40,7 @@ import SteamLoginButton from "./SteamLoginButton";
|
||||
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||
import OktaLoginButton from "./OktaLoginButton";
|
||||
import DouyinLoginButton from "./DouyinLoginButton";
|
||||
import KwaiLoginButton from "./KwaiLoginButton";
|
||||
import LoginButton from "./LoginButton";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import {WechatOfficialAccountModal} from "./Util";
|
||||
@ -96,6 +97,8 @@ function getSigninButton(provider) {
|
||||
return <OktaLoginButton text={text} align={"center"} />;
|
||||
} else if (provider.type === "Douyin") {
|
||||
return <DouyinLoginButton text={text} align={"center"} />;
|
||||
} else if (provider.type === "Kwai") {
|
||||
return <KwaiLoginButton text={text} align={"center"} />;
|
||||
} else {
|
||||
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
||||
}
|
||||
|
Reference in New Issue
Block a user