Compare commits

...

44 Commits

Author SHA1 Message Date
Yang Luo
cba338eef2 Merge pull request #973 from qianxi0410/eslint
feat(web): add some eslint rules
2022-08-07 00:41:51 +08:00
qianxi0410
c428de6e42 feat: fix some comma dangle 2022-08-07 00:17:27 +08:00
qianxi0410
9bca6bb72e feat: no-multi-spacing 2022-08-07 00:06:20 +08:00
qianxi0410
cd966116d4 feat: comma dangle 2022-08-06 23:54:56 +08:00
qianxi0410
9abf1b9d73 feat: key spacing 2022-08-06 23:47:28 +08:00
qianxi0410
6aaba6debd feat: space between infix op 2022-08-06 23:43:09 +08:00
qianxi0410
77565712e0 feat: no-multi-empty-lines 2022-08-06 23:38:03 +08:00
qianxi0410
d025259db7 feat: indent 2022-08-06 23:36:20 +08:00
Artem
aafdc546fa fix: panic when creating a user in a non-existent org (#969) 2022-08-06 22:30:56 +08:00
q1anx1
539ca2d731 chore(web): add fix command (#964) 2022-08-05 23:40:04 +08:00
Ryao
ea326b3513 fix: show social buttons on signup page (#962) 2022-08-05 18:59:56 +08:00
Товарищ программист
98ef766fb4 fix: fix webauthn entry cannot add bug (#960)
* fix: fix webauthn

* Update LoginPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-08-05 17:43:04 +08:00
Gucheng Wang
e94ada9ea2 Fix new accountItem. 2022-08-05 15:36:07 +08:00
Resulte Lee
4ea482223d feat: add geetest captcha (#953) 2022-08-04 20:55:04 +08:00
Gucheng Wang
d55ae7d1d2 Enable some other DBs 2022-08-04 20:28:09 +08:00
imp2002
d72e00605f fix: updateProviderField when add provider payment (#952) 2022-08-04 19:39:25 +08:00
zzjin
be74cb621f feat: Support sub-directory (#943)
By adding PUBLIC_URL to relative `.`

Signed-off-by: zzjin <tczzjin@gmail.com>
2022-08-02 00:21:15 +08:00
q1anx1
13404d6035 feat: fix binding after registration causes the page to crash (#945) 2022-08-01 21:08:10 +08:00
Mikey
afa9c530ad fix: panic triggered when user is nil (#940) 2022-07-31 23:23:36 +08:00
Yang Luo
1600615aca Support sqlite3 DB 2022-07-31 18:11:18 +08:00
Mikey
2bb8491499 fix: unable to get user if profile is private (#936) 2022-07-31 10:54:41 +08:00
Mikey
293283ed25 feat: add get user by phone (#934)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

* feat: add get user by phone

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-31 01:02:28 +08:00
q1anx1
9cb519d1e9 fix: Admins should not be allowed to add third-party login for their members (#932)
* feat: admin can unlink the other user

* feat: global admin can unlink other user

* fix
2022-07-30 23:11:02 +08:00
Yixiang Zhao
fb9b8f1662 fix: skip the duplicated users when sync users (#928)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-30 22:24:23 +08:00
Mikey
2fec3f72ae fix: check reset phone & email modify rules (#927)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-30 18:17:13 +08:00
Yang Luo
11695220a8 Use user.GetId() 2022-07-30 17:40:30 +08:00
Resulte Lee
155660b0d7 feat: get user api return roles and permissions (#929) 2022-07-30 17:31:56 +08:00
imp2002
1c72f5300c feat: fix 'Enable code sign' is not displayed in the login page (#925) 2022-07-28 23:11:33 +08:00
q1anx1
3dd56195d9 fix: fix the problem of link error (#923) 2022-07-28 21:52:10 +08:00
Resulte Lee
8865244262 fix: add oauth login auto close page (#915) 2022-07-26 23:03:55 +08:00
Yixiang Zhao
3400fa1e9c feat: support local login for non-built-in users (#911)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-26 19:27:24 +08:00
Resulte Lee
bdc5c92ef0 fix: send code missing parameter & show more detail responseError (#910) 2022-07-25 23:46:38 +08:00
SLingyu
4e3eedf246 feat: fix bug that the default permission prevents admin to login in (#907)
* fix:The certs page is displayed incorrectly

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name

* fix: Default action prevents admin to login in
2022-07-24 23:36:55 +08:00
SLingyu
8e98fc5a9f feat: rename all publicKey occurrences to certificate (#894)
* fix:The certs page is displayed incorrectly

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name
2022-07-23 09:40:51 +08:00
leoshine
6f6159be07 feat: add GET method of logout API (#903) 2022-07-22 21:13:49 +08:00
Gucheng Wang
3e4dbc2dcb fix: URL bug in getUploadFileUrl function 2022-07-20 17:49:11 +08:00
Yixiang Zhao
48b5b27982 fix: invalid redirect url after sign up (#896)
* fix: invalid redirect url after sign up

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* Update App.js

* Update Setting.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-19 23:31:17 +08:00
q1anx1
1839252c30 chore(web): sort import members (#895) 2022-07-18 20:57:38 +08:00
q1anx1
1fff1db6a7 fix(web): fix the bug of infinity loop animate when unauthorized (#891)
* fix(web): fix the bug of infinity loop when unauthorized

* fix

* fix

* fix

* Update BaseListPage.js

* Update OrganizationListPage.js

* Update OrganizationListPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-17 18:20:52 +08:00
Yang Luo
a0b0e186b7 Improve i18n code and data. 2022-07-17 17:56:43 +08:00
Yang Luo
8c7f235ee1 Fix bug in uploadFile()'s URL. 2022-07-17 14:29:06 +08:00
waltcow
a0a762aa6f fix: typo in field tag in BilibiliUserInfo (#890) 2022-07-17 11:31:43 +08:00
Yixiang Zhao
2eec53a6d0 fix: actions initialized to null and model/resources not updated with the owner (#887)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-16 15:00:42 +08:00
Yixiang Zhao
117dec4542 feat: failed to sync keycloak users in the PostgreSQL database (#886)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-16 12:14:35 +08:00
132 changed files with 1463 additions and 659 deletions

View File

@@ -78,6 +78,7 @@ p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, * p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, * p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, * p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, GET, /api/get-account, *, * p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, * p, *, *, GET, /api/userinfo, *, *
p, *, *, *, /api/login/oauth, *, * p, *, *, *, /api/login/oauth, *, *
@@ -92,6 +93,7 @@ p, *, *, GET, /api/get-payment, *, *
p, *, *, POST, /api/update-payment, *, * p, *, *, POST, /api/update-payment, *, *
p, *, *, POST, /api/invoice-payment, *, * p, *, *, POST, /api/invoice-payment, *, *
p, *, *, GET, /api/get-providers, *, * p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/notify-payment, *, *
p, *, *, POST, /api/unlink, *, * p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, * p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, * p, *, *, POST, /api/send-verification-code, *, *

82
captcha/geetest.go Normal file
View File

@@ -0,0 +1,82 @@
// Copyright 2022 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 captcha
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"github.com/casdoor/casdoor/util"
)
const GEETESTCaptchaVerifyUrl = "http://gcaptcha4.geetest.com/validate"
type GEETESTCaptchaProvider struct {
}
func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
captcha := &GEETESTCaptchaProvider{}
return captcha
}
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
pathData, err := url.ParseQuery(token)
if err != nil {
return false, err
}
signToken := util.GetHmacSha256(clientSecret, pathData["lot_number"][0])
formData := make(url.Values)
formData["lot_number"] = []string{pathData["lot_number"][0]}
formData["captcha_output"] = []string{pathData["captcha_output"][0]}
formData["pass_token"] = []string{pathData["pass_token"][0]}
formData["gen_time"] = []string{pathData["gen_time"][0]}
formData["sign_token"] = []string{signToken}
captchaId := pathData["captcha_id"][0]
cli := http.Client{Timeout: time.Second * 5}
resp, err := cli.PostForm(fmt.Sprintf("%s?captcha_id=%s", GEETESTCaptchaVerifyUrl, captchaId), formData)
if err != nil || resp.StatusCode != 200 {
return false, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Result string `json:"result"`
Reason string `json:"reason"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if captchaResp.Result == "success" {
return true, nil
}
return false, errors.New(captchaResp.Reason)
}

View File

@@ -27,6 +27,8 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
return NewHCaptchaProvider() return NewHCaptchaProvider()
} else if captchaType == "Aliyun Captcha" { } else if captchaType == "Aliyun Captcha" {
return NewAliyunCaptchaProvider() return NewAliyunCaptchaProvider()
} else if captchaType == "GEETEST" {
return NewGEETESTCaptchaProvider()
} }
return nil return nil
} }

View File

@@ -217,7 +217,7 @@ func (c *ApiController) Signup() {
record.User = user.Name record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) }) util.SafeGoroutine(func() { object.AddRecord(record) })
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name) userId := user.GetId()
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId) util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId) c.ResponseOk(userId)
@@ -228,7 +228,7 @@ func (c *ApiController) Signup() {
// @Tag Login API // @Tag Login API
// @Description logout the current user // @Description logout the current user
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /logout [post] // @router /logout [get,post]
func (c *ApiController) Logout() { func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)

View File

@@ -21,7 +21,8 @@ import (
) )
type LinkForm struct { type LinkForm struct {
ProviderType string `json:"providerType"` ProviderType string `json:"providerType"`
User object.User `json:"user"`
} }
// Unlink ... // Unlink ...
@@ -40,16 +41,55 @@ func (c *ApiController) Unlink() {
} }
providerType := form.ProviderType providerType := form.ProviderType
// the user will be unlinked from the provider
unlinkedUser := form.User
user := object.GetUser(userId) user := object.GetUser(userId)
value := object.GetUserField(user, providerType)
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
c.ResponseError("You are not the global admin, you can't unlink other users")
return
}
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
application := object.GetApplicationByUser(user)
if application == nil {
c.ResponseError("You can't unlink yourself, you are not a member of any application")
return
}
if len(application.Providers) == 0 {
c.ResponseError("This application has no providers")
return
}
provider := application.GetProviderItemByType(providerType)
if provider == nil {
c.ResponseError("This application has no providers of type " + providerType)
return
}
if !provider.CanUnlink {
c.ResponseError("This provider can't be unlinked")
return
}
}
// only two situations can happen here
// 1. the user is the global admin
// 2. the user is unlinking themselves and provider can be unlinked
value := object.GetUserField(&unlinkedUser, providerType)
if value == "" { if value == "" {
c.ResponseError("Please link first", value) c.ResponseError("Please link first", value)
return return
} }
object.ClearUserOAuthProperties(user, providerType) object.ClearUserOAuthProperties(&unlinkedUser, providerType)
object.LinkUserAccount(user, providerType, "") object.LinkUserAccount(&unlinkedUser, providerType, "")
c.ResponseOk() c.ResponseOk()
} }

View File

@@ -80,12 +80,16 @@ func (c *ApiController) GetUsers() {
// @Title GetUser // @Title GetUser
// @Tag User API // @Tag User API
// @Description get user // @Description get user
// @Param id query string true "The id of the user" // @Param id query string true "The id of the user"
// @Param owner query string false "The owner of the user"
// @Param email query string false "The email of the user"
// @Param phone query string false "The phone of the user"
// @Success 200 {object} object.User The Response object // @Success 200 {object} object.User The Response object
// @router /get-user [get] // @router /get-user [get]
func (c *ApiController) GetUser() { func (c *ApiController) GetUser() {
id := c.Input().Get("id") id := c.Input().Get("id")
email := c.Input().Get("email") email := c.Input().Get("email")
phone := c.Input().Get("phone")
userId := c.Input().Get("userId") userId := c.Input().Get("userId")
owner := c.Input().Get("owner") owner := c.Input().Get("owner")
@@ -96,7 +100,7 @@ func (c *ApiController) GetUser() {
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner)) organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
if !organization.IsProfilePublic { if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false) hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false)
if !hasPermission { if !hasPermission {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -104,14 +108,24 @@ func (c *ApiController) GetUser() {
} }
var user *object.User var user *object.User
if email != "" { switch {
case email != "":
user = object.GetUserByEmail(owner, email) user = object.GetUserByEmail(owner, email)
} else if userId != "" { case phone != "":
user = object.GetUserByPhone(owner, phone)
case userId != "":
user = object.GetUserByUserId(owner, userId) user = object.GetUserByUserId(owner, userId)
} else { default:
user = object.GetUser(id) user = object.GetUser(id)
} }
if user != nil {
roles := object.GetRolesByUser(user.GetId())
user.Roles = roles
permissions := object.GetPermissionsByUser(user.GetId())
user.Permissions = permissions
}
c.Data["json"] = object.GetMaskedUser(user) c.Data["json"] = object.GetMaskedUser(user)
c.ServeJSON() c.ServeJSON()
} }
@@ -252,7 +266,7 @@ func (c *ApiController) SetPassword() {
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
userId := fmt.Sprintf("%s/%s", userOwner, userName) userId := fmt.Sprintf("%s/%s", userOwner, userName)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true) hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true)
if !hasPermission { if !hasPermission {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@@ -49,8 +49,24 @@ func (c *ApiController) SendVerificationCode() {
applicationId := c.Ctx.Request.Form.Get("applicationId") applicationId := c.Ctx.Request.Form.Get("applicationId")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" || dest == "" || applicationId == "" || !strings.Contains(applicationId, "/") || checkType == "" { if destType == "" {
c.ResponseError("Missing parameter.") c.ResponseError("Missing parameter: type.")
return
}
if dest == "" {
c.ResponseError("Missing parameter: dest.")
return
}
if applicationId == "" {
c.ResponseError("Missing parameter: applicationId.")
return
}
if !strings.Contains(applicationId, "/") {
c.ResponseError("Wrong parameter: applicationId.")
return
}
if checkType == "" {
c.ResponseError("Missing parameter: checkType.")
return return
} }
@@ -152,13 +168,35 @@ func (c *ApiController) ResetEmailOrPhone() {
} }
checkDest := dest checkDest := dest
org := object.GetOrganizationByUser(user)
if destType == "phone" { if destType == "phone" {
org := object.GetOrganizationByUser(user) phoneItem := object.GetAccountItemByName("Phone", org)
if phoneItem == nil {
c.ResponseError("Unable to get the phone modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user); !pass {
c.ResponseError(errMsg)
return
}
phonePrefix := "86" phonePrefix := "86"
if org != nil && org.PhonePrefix != "" { if org != nil && org.PhonePrefix != "" {
phonePrefix = org.PhonePrefix phonePrefix = org.PhonePrefix
} }
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest) checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
} else if destType == "email" {
emailItem := object.GetAccountItemByName("Email", org)
if emailItem == nil {
c.ResponseError("Unable to get the email modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user); !pass {
c.ResponseError(errMsg)
return
}
} }
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 { if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
c.ResponseError(ret) c.ResponseError(ret)

2
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/casdoor/goth v1.69.0-FIX2 github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0 github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-ldap/ldap/v3 v3.3.0
@@ -23,6 +24,7 @@ require (
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0 github.com/lestrrat-go/jwx v0.9.0
github.com/lib/pq v1.8.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1

2
go.sum
View File

@@ -125,6 +125,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b h1:L63RATZFZuFMXy6ixnKmv3eNAXwYQF6HW1vd4IYsQqQ= github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b h1:L63RATZFZuFMXy6ixnKmv3eNAXwYQF6HW1vd4IYsQqQ=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ= github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
@@ -173,6 +174,7 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=

View File

@@ -39,6 +39,7 @@ func readI18nFile(language string) *I18nData {
func writeI18nFile(language string, data *I18nData) { func writeI18nFile(language string, data *I18nData) {
s := util.StructToJsonFormatted(data) s := util.StructToJsonFormatted(data)
s = strings.ReplaceAll(s, "\\u0026", "&") s = strings.ReplaceAll(s, "\\u0026", "&")
s += "\n"
println(s) println(s)
util.WriteStringToPath(s, getI18nFilePath(language)) util.WriteStringToPath(s, getI18nFilePath(language))

View File

@@ -144,7 +144,7 @@ func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
type BilibiliUserInfo struct { type BilibiliUserInfo struct {
Name string `json:"name"` Name string `json:"name"`
Face string `json:"face"` Face string `json:"face"`
OpenId string `json:"openid` OpenId string `json:"openid"`
} }
type BilibiliUserInfoResponse struct { type BilibiliUserInfoResponse struct {

View File

@@ -139,7 +139,7 @@
"cryptoAlgorithm": "RS256", "cryptoAlgorithm": "RS256",
"bitSize": 4096, "bitSize": 4096,
"expireInYears": 20, "expireInYears": 20,
"publicKey": "", "certificate": "",
"privateKey": "" "privateKey": ""
} }
], ],

View File

@@ -21,9 +21,10 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
//_ "github.com/denisenkom/go-mssqldb" // db = mssql _ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql _ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres _ "github.com/lib/pq" // db = postgres
//_ "github.com/mattn/go-sqlite3" // db = sqlite3
"xorm.io/core" "xorm.io/core"
"xorm.io/xorm" "xorm.io/xorm"
) )

View File

@@ -33,7 +33,7 @@ type Cert struct {
BitSize int `json:"bitSize"` BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"` ExpireInYears int `json:"expireInYears"`
PublicKey string `xorm:"mediumtext" json:"publicKey"` Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"` PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"` AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"` AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
@@ -123,9 +123,9 @@ func UpdateCert(id string, cert *Cert) bool {
} }
func AddCert(cert *Cert) bool { func AddCert(cert *Cert) bool {
if cert.PublicKey == "" || cert.PrivateKey == "" { if cert.Certificate == "" || cert.PrivateKey == "" {
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner) certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
cert.PublicKey = publicKey cert.Certificate = certificate
cert.PrivateKey = privateKey cert.PrivateKey = privateKey
} }

View File

@@ -197,14 +197,18 @@ func filterField(field string) bool {
return reFieldWhiteList.MatchString(field) return reFieldWhiteList.MatchString(field)
} }
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) { func CheckUserPermission(requestUserId, userId, userOwner string, strict bool) (bool, error) {
if requestUserId == "" { if requestUserId == "" {
return false, fmt.Errorf("please login first") return false, fmt.Errorf("please login first")
} }
targetUser := GetUser(userId) if userId != "" {
if targetUser == nil { targetUser := GetUser(userId)
return false, fmt.Errorf("the user: %s doesn't exist", userId) if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
}
userOwner = targetUser.Owner
} }
hasPermission := false hasPermission := false
@@ -219,7 +223,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error
hasPermission = true hasPermission = true
} else if requestUserId == userId { } else if requestUserId == userId {
hasPermission = true hasPermission = true
} else if targetUser.Owner == requestUser.Owner { } else if userOwner == requestUser.Owner {
if strict { if strict {
hasPermission = requestUser.IsAdmin hasPermission = requestUser.IsAdmin
} else { } else {
@@ -236,7 +240,7 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
allowed := true allowed := true
var err error var err error
for _, permission := range permissions { for _, permission := range permissions {
if !permission.IsEnabled { if !permission.IsEnabled || len(permission.Users) == 0 {
continue continue
} }

View File

@@ -25,6 +25,7 @@ import (
func InitDb() { func InitDb() {
existed := initBuiltInOrganization() existed := initBuiltInOrganization()
if !existed { if !existed {
initBuiltInPermission()
initBuiltInProvider() initBuiltInProvider()
initBuiltInUser() initBuiltInUser()
initBuiltInApplication() initBuiltInApplication()
@@ -70,6 +71,8 @@ func initBuiltInOrganization() bool {
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
@@ -167,7 +170,7 @@ func readTokenFromFile() (string, string) {
} }
func initBuiltInCert() { func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile() tokenJwtCertificate, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in") cert := getCert("admin", "cert-built-in")
if cert != nil { if cert != nil {
return return
@@ -183,7 +186,7 @@ func initBuiltInCert() {
CryptoAlgorithm: "RS256", CryptoAlgorithm: "RS256",
BitSize: 4096, BitSize: 4096,
ExpireInYears: 20, ExpireInYears: 20,
PublicKey: tokenJwtPublicKey, Certificate: tokenJwtCertificate,
PrivateKey: tokenJwtPrivateKey, PrivateKey: tokenJwtPrivateKey,
} }
AddCert(cert) AddCert(cert)
@@ -230,3 +233,25 @@ func initBuiltInProvider() {
func initWebAuthn() { func initWebAuthn() {
gob.Register(webauthn.SessionData{}) gob.Register(webauthn.SessionData{})
} }
func initBuiltInPermission() {
permission := GetPermission("built-in/permission-built-in")
if permission != nil {
return
}
permission = &Permission{
Owner: "built-in",
Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission",
Users: []string{"built-in/admin"},
Roles: []string{},
ResourceType: "Application",
Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"},
Effect: "Allow",
IsEnabled: true,
}
AddPermission(permission)
}

View File

@@ -89,6 +89,8 @@ func initDefinedOrganization(organization *Organization) {
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},

View File

@@ -97,7 +97,7 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html //link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key //or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
for _, cert := range certs { for _, cert := range certs {
certPemBlock := []byte(cert.PublicKey) certPemBlock := []byte(cert.Certificate)
certDerBlock, _ := pem.Decode(certPemBlock) certDerBlock, _ := pem.Decode(certPemBlock)
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes) x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)

View File

@@ -15,6 +15,8 @@
package object package object
import ( import (
"fmt"
"github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
@@ -186,3 +188,31 @@ func DeleteOrganization(organization *Organization) bool {
func GetOrganizationByUser(user *User) *Organization { func GetOrganizationByUser(user *User) *Organization {
return getOrganization("admin", user.Owner) return getOrganization("admin", user.Owner)
} }
func GetAccountItemByName(name string, organization *Organization) *AccountItem {
if organization == nil {
return nil
}
for _, accountItem := range organization.AccountItems {
if accountItem.Name == name {
return accountItem
}
}
return nil
}
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User) (bool, string) {
switch accountItem.ModifyRule {
case "Admin":
if !(user.IsAdmin || user.IsGlobalAdmin) {
return false, fmt.Sprintf("Only admin can modify the %s.", accountItem.Name)
}
case "Immutable":
return false, fmt.Sprintf("The %s is immutable.", accountItem.Name)
case "Self":
break
default:
return false, fmt.Sprintf("Unknown modify rule %s.", accountItem.ModifyRule)
}
return true, ""
}

View File

@@ -229,3 +229,13 @@ func removePolicies(permission *Permission) {
panic(err) panic(err)
} }
} }
func GetPermissionsByUser(userId string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}

View File

@@ -30,7 +30,7 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123") product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay") provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay") cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey) pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
paymentName := util.GenerateTimeId() paymentName := util.GenerateTimeId()
returnUrl := "" returnUrl := ""

View File

@@ -214,7 +214,7 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
} }
} }
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey) pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil { if pProvider == nil {
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type) return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
} }

View File

@@ -33,6 +33,15 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
return nil return nil
} }
func (application *Application) GetProviderItemByType(providerType string) *ProviderItem {
for _, item := range application.Providers {
if item.Provider.Type == providerType {
return item
}
}
return nil
}
func (pi *ProviderItem) IsProviderVisible() bool { func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil { if pi.Provider == nil {
return false return false

View File

@@ -121,3 +121,13 @@ func DeleteRole(role *Role) bool {
func (role *Role) GetId() string { func (role *Role) GetId() string {
return fmt.Sprintf("%s/%s", role.Owner, role.Name) return fmt.Sprintf("%s/%s", role.Owner, role.Name)
} }
func GetRolesByUser(userId string) []*Role {
roles := []*Role{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&roles)
if err != nil {
panic(err)
}
return roles
}

View File

@@ -36,7 +36,7 @@ import (
) )
//returns a saml2 response //returns a saml2 response
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) { func NewSamlResponse(user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{ samlResponse := &etree.Element{
Space: "samlp", Space: "samlp",
Tag: "Response", Tag: "Response",
@@ -177,8 +177,8 @@ type Attribute struct {
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) { func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
//_, originBackend := getOriginFromHost(host) //_, originBackend := getOriginFromHost(host)
cert := getCertByApplication(application) cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey)) block, _ := pem.Decode([]byte(cert.Certificate))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
origin := beego.AppConfig.String("origin") origin := beego.AppConfig.String("origin")
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
@@ -199,7 +199,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
KeyInfo: KeyInfo{ KeyInfo: KeyInfo{
X509Data: X509Data{ X509Data: X509Data{
X509Certificate: X509Certificate{ X509Certificate: X509Certificate{
Cert: publicKey, Cert: certificate,
}, },
}, },
}, },
@@ -248,18 +248,18 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
return "", "", fmt.Errorf("err: invalid issuer url") return "", "", fmt.Errorf("err: invalid issuer url")
} }
// get public key string // get certificate string
cert := getCertByApplication(application) cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey)) block, _ := pem.Decode([]byte(cert.Certificate))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
// build signedResponse // build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris) samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{ randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey, PrivateKey: cert.PrivateKey,
X509Certificate: publicKey, X509Certificate: certificate,
} }
ctx := dsig.NewDefaultSigningContext(randomKeyStore) ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1 ctx.Hash = crypto.SHA1

View File

@@ -72,9 +72,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
host = fmt.Sprintf("%s/%s", host, provider.Bucket) host = fmt.Sprintf("%s/%s", host, provider.Bucket)
} }
fileUrl := util.UrlJoin(host, objectKey) fileUrl := util.UrlJoin(host, escapePath(objectKey))
if hasTimestamp { if hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime()) fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
} }
return fileUrl, objectKey return fileUrl, objectKey

View File

@@ -22,7 +22,7 @@ import (
func (syncer *Syncer) syncUsers() { func (syncer *Syncer) syncUsers() {
fmt.Printf("Running syncUsers()..\n") fmt.Printf("Running syncUsers()..\n")
users, userMap := syncer.getUserMap() users, userMap, userNameMap := syncer.getUserMap()
oUsers, oUserMap, err := syncer.getOriginalUserMap() oUsers, oUserMap, err := syncer.getOriginalUserMap()
if err != nil { if err != nil {
fmt.Printf(err.Error()) fmt.Printf(err.Error())
@@ -44,9 +44,11 @@ func (syncer *Syncer) syncUsers() {
for _, oUser := range oUsers { for _, oUser := range oUsers {
id := oUser.Id id := oUser.Id
if _, ok := userMap[id]; !ok { if _, ok := userMap[id]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap) if _, ok := userNameMap[oUser.Name]; !ok {
fmt.Printf("New user: %v\n", newUser) newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
newUsers = append(newUsers, newUser) fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
}
} else { } else {
user := userMap[id] user := userMap[id]
oHash := syncer.calculateHash(oUser) oHash := syncer.calculateHash(oUser)

View File

@@ -151,6 +151,8 @@ func (syncer *Syncer) initAdapter() {
var dataSourceName string var dataSourceName string
if syncer.DatabaseType == "mssql" { if syncer.DatabaseType == "mssql" {
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database) dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else if syncer.DatabaseType == "postgres" {
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else { } else {
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port) dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
} }

View File

@@ -173,16 +173,21 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
} }
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {
tableColumnName := tableColumn.Name
if syncer.Type == "Keycloak" && syncer.DatabaseType == "postgres" {
tableColumnName = strings.ToLower(tableColumnName)
}
value := "" value := ""
if strings.Contains(tableColumn.Name, "+") { if strings.Contains(tableColumnName, "+") {
names := strings.Split(tableColumn.Name, "+") names := strings.Split(tableColumnName, "+")
var values []string var values []string
for _, name := range names { for _, name := range names {
values = append(values, result[strings.Trim(name, " ")]) values = append(values, result[strings.Trim(name, " ")])
} }
value = strings.Join(values, " ") value = strings.Join(values, " ")
} else { } else {
value = result[tableColumn.Name] value = result[tableColumnName]
} }
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value) syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
} }
@@ -198,7 +203,7 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
originalUser.PasswordSalt = credential.Salt originalUser.PasswordSalt = credential.Salt
} }
// query and set signup application from user group table // query and set signup application from user group table
sql = fmt.Sprintf("select name from keycloak_group where id = " + sql = fmt.Sprintf("select name from keycloak_group where id = "+
"(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id) "(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id)
groupResult, _ := syncer.Adapter.Engine.QueryString(sql) groupResult, _ := syncer.Adapter.Engine.QueryString(sql)
if len(groupResult) > 0 { if len(groupResult) > 0 {
@@ -209,7 +214,12 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
tm := time.Unix(i/int64(1000), 0) tm := time.Unix(i/int64(1000), 0)
originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00") originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00")
// enable // enable
originalUser.IsForbidden = !(result["ENABLED"] == "\x01") value, ok := result["ENABLED"]
if ok {
originalUser.IsForbidden = !util.ParseBool(value)
} else {
originalUser.IsForbidden = !util.ParseBool(result["enabled"])
}
} }
users = append(users, originalUser) users = append(users, originalUser)

View File

@@ -19,12 +19,15 @@ func (syncer *Syncer) getUsers() []*User {
return users return users
} }
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) { func (syncer *Syncer) getUserMap() ([]*User, map[string]*User, map[string]*User) {
users := syncer.getUsers() users := syncer.getUsers()
m := map[string]*User{} m1 := map[string]*User{}
m2 := map[string]*User{}
for _, user := range users { for _, user := range users {
m[user.Id] = user m1[user.Id] = user
m2[user.Name] = user
} }
return users, m
return users, m1, m2
} }

View File

@@ -241,11 +241,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
samlResponse := NewSamlResponse11(user, request.RequestID, host) samlResponse := NewSamlResponse11(user, request.RequestID, host)
cert := getCertByApplication(application) cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey)) block, _ := pem.Decode([]byte(cert.Certificate))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
randomKeyStore := &X509Key{ randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey, PrivateKey: cert.PrivateKey,
X509Certificate: publicKey, X509Certificate: certificate,
} }
ctx := dsig.NewDefaultSigningContext(randomKeyStore) ctx := dsig.NewDefaultSigningContext(randomKeyStore)

View File

@@ -129,13 +129,13 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
} }
// RSA public key // RSA certificate
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey)) certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return publicKey, nil return certificate, nil
}) })
if t != nil { if t != nil {

View File

@@ -23,10 +23,10 @@ import (
func TestGenerateRsaKeys(t *testing.T) { func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key" fileId := "token_jwt_key"
publicKey, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization") certificate, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
// Write certificate (aka public key) to file. // Write certificate (aka certificate) to file.
util.WriteStringToPath(publicKey, fmt.Sprintf("%s.pem", fileId)) util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file. // Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId)) util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))

View File

@@ -73,7 +73,7 @@ type User struct {
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"` LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"` LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"` GitHub string `xorm:"github varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"` Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"` QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"` WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
@@ -104,6 +104,9 @@ type User struct {
Ldap string `xorm:"ldap varchar(100)" json:"ldap"` Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"` Properties map[string]string `json:"properties"`
Roles []*Role `json:"roles"`
Permissions []*Permission `json:"permissions"`
} }
type Userinfo struct { type Userinfo struct {
@@ -270,6 +273,24 @@ func GetUserByEmail(owner string, email string) *User {
} }
} }
func GetUserByPhone(owner string, phone string) *User {
if owner == "" || phone == "" {
return nil
}
user := User{Owner: owner, Phone: phone}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUserByUserId(owner string, userId string) *User { func GetUserByUserId(owner string, userId string) *User {
if owner == "" || userId == "" { if owner == "" || userId == "" {
return nil return nil
@@ -396,6 +417,10 @@ func AddUser(user *User) bool {
} }
organization := GetOrganizationByUser(user) organization := GetOrganizationByUser(user)
if organization == nil {
return false
}
user.UpdateUserPassword(organization) user.UpdateUserPassword(organization)
user.UpdateUserHash() user.UpdateUserHash()

View File

@@ -37,11 +37,11 @@ func TestSyncAvatarsFromGitHub(t *testing.T) {
users := GetGlobalUsers() users := GetGlobalUsers()
for _, user := range users { for _, user := range users {
if user.Github == "" { if user.GitHub == "" {
continue continue
} }
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.Github) user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.GitHub)
updateUserColumn("avatar", user) updateUserColumn("avatar", user)
} }
} }

View File

@@ -106,6 +106,10 @@ func setUserProperty(user *User, field string, value string) {
if value == "" { if value == "" {
delete(user.Properties, field) delete(user.Properties, field)
} else { } else {
if user.Properties == nil {
user.Properties = make(map[string]string)
}
user.Properties[field] = value user.Properties[field] = value
} }
} }

View File

@@ -28,7 +28,7 @@ type AlipayPaymentProvider struct {
Client *alipay.Client Client *alipay.Client
} }
func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider { func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
pp := &AlipayPaymentProvider{} pp := &AlipayPaymentProvider{}
client, err := alipay.NewClient(appId, appPrivateKey, true) client, err := alipay.NewClient(appId, appPrivateKey, true)
@@ -36,7 +36,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
panic(err) panic(err)
} }
err = client.SetCertSnByContent([]byte(appPublicKey), []byte(authorityRootPublicKey), []byte(authorityPublicKey)) err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -22,9 +22,9 @@ type PaymentProvider interface {
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
} }
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider { func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
if typ == "Alipay" { if typ == "Alipay" {
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey) return NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
} else if typ == "GC" { } else if typ == "GC" {
return NewGcPaymentProvider(appId, clientSecret, host) return NewGcPaymentProvider(appId, clientSecret, host)
} }

View File

@@ -122,6 +122,10 @@ func AuthzFilter(ctx *context.Context) {
urlPath := getUrlPath(ctx.Request.URL.Path) urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx) objOwner, objName := getObject(ctx)
if strings.HasPrefix(urlPath, "/api/notify-payment") {
urlPath = "/api/notify-payment"
}
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName) isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
result := "deny" result := "deny"

View File

@@ -48,7 +48,7 @@ func initAPI() {
beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup") beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup")
beego.Router("/api/login", &controllers.ApiController{}, "POST:Login") beego.Router("/api/login", &controllers.ApiController{}, "POST:Login")
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin") beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout") beego.Router("/api/logout", &controllers.ApiController{}, "GET,POST:Logout")
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount") beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo") beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink") beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")

View File

@@ -2418,6 +2418,21 @@
} }
}, },
"/api/logout": { "/api/logout": {
"get": {
"tags": [
"Login API"
],
"description": "logout the current user",
"operationId": "ApiController.Logout",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
},
"post": { "post": {
"tags": [ "tags": [
"Login API" "Login API"
@@ -3096,14 +3111,120 @@
], ],
"operationId": "ApiController.VerifyCaptcha" "operationId": "ApiController.VerifyCaptcha"
} }
},
"/api/webauthn/signin/begin": {
"get": {
"tags": [
"Login API"
],
"description": "WebAuthn Login Flow 1st stage",
"operationId": "ApiController.WebAuthnSigninBegin",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "owner",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "name",
"description": "name",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The CredentialAssertion object",
"schema": {
"$ref": "#/definitions/protocol.CredentialAssertion"
}
}
}
}
},
"/api/webauthn/signin/finish": {
"post": {
"tags": [
"Login API"
],
"description": "WebAuthn Login Flow 2nd stage",
"operationId": "ApiController.WebAuthnSigninBegin",
"parameters": [
{
"in": "body",
"name": "body",
"description": "authenticator assertion Response",
"required": true,
"schema": {
"$ref": "#/definitions/protocol.CredentialAssertionResponse"
}
}
],
"responses": {
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
},
"/api/webauthn/signup/begin": {
"get": {
"tags": [
"User API"
],
"description": "WebAuthn Registration Flow 1st stage",
"operationId": "ApiController.WebAuthnSignupBegin",
"responses": {
"200": {
"description": "The CredentialCreationOptions object",
"schema": {
"$ref": "#/definitions/protocol.CredentialCreation"
}
}
}
}
},
"/api/webauthn/signup/finish": {
"post": {
"tags": [
"User API"
],
"description": "WebAuthn Registration Flow 2nd stage",
"operationId": "ApiController.WebAuthnSignupFinish",
"parameters": [
{
"in": "body",
"name": "body",
"description": "authenticator attestation Response",
"required": true,
"schema": {
"$ref": "#/definitions/protocol.CredentialCreationResponse"
}
}
],
"responses": {
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
"2127.0xc000398090.false": { "2127.0xc000427560.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
"2161.0xc0003980c0.false": { "2161.0xc000427590.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@@ -3221,10 +3342,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2127.0xc000398090.false" "$ref": "#/definitions/2127.0xc000427560.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2161.0xc0003980c0.false" "$ref": "#/definitions/2161.0xc000427590.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@@ -3329,12 +3450,18 @@
"enablePassword": { "enablePassword": {
"type": "boolean" "type": "boolean"
}, },
"enableSamlCompress": {
"type": "boolean"
},
"enableSignUp": { "enableSignUp": {
"type": "boolean" "type": "boolean"
}, },
"enableSigninSession": { "enableSigninSession": {
"type": "boolean" "type": "boolean"
}, },
"enableWebAuthn": {
"type": "boolean"
},
"expireInHours": { "expireInHours": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
@@ -3444,7 +3571,7 @@
"privateKey": { "privateKey": {
"type": "string" "type": "string"
}, },
"publicKey": { "certificate": {
"type": "string" "type": "string"
}, },
"scope": { "scope": {
@@ -4507,6 +4634,12 @@
"updatedTime": { "updatedTime": {
"type": "string" "type": "string"
}, },
"webauthnCredentials": {
"type": "array",
"items": {
"$ref": "#/definitions/webauthn.Credential"
}
},
"wechat": { "wechat": {
"type": "string" "type": "string"
}, },
@@ -4596,6 +4729,26 @@
} }
} }
}, },
"protocol.CredentialAssertion": {
"title": "CredentialAssertion",
"type": "object"
},
"protocol.CredentialAssertionResponse": {
"title": "CredentialAssertionResponse",
"type": "object"
},
"protocol.CredentialCreation": {
"title": "CredentialCreation",
"type": "object"
},
"protocol.CredentialCreationResponse": {
"title": "CredentialCreationResponse",
"type": "object"
},
"webauthn.Credential": {
"title": "Credential",
"type": "object"
},
"xorm.Engine": { "xorm.Engine": {
"title": "Engine", "title": "Engine",
"type": "object" "type": "object"

View File

@@ -1584,6 +1584,16 @@ paths:
schema: schema:
$ref: '#/definitions/object.TokenError' $ref: '#/definitions/object.TokenError'
/api/logout: /api/logout:
get:
tags:
- Login API
description: logout the current user
operationId: ApiController.Logout
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
post: post:
tags: tags:
- Login API - Login API
@@ -2028,11 +2038,80 @@ paths:
tags: tags:
- Verification API - Verification API
operationId: ApiController.VerifyCaptcha operationId: ApiController.VerifyCaptcha
/api/webauthn/signin/begin:
get:
tags:
- Login API
description: WebAuthn Login Flow 1st stage
operationId: ApiController.WebAuthnSigninBegin
parameters:
- in: query
name: owner
description: owner
required: true
type: string
- in: query
name: name
description: name
required: true
type: string
responses:
"200":
description: The CredentialAssertion object
schema:
$ref: '#/definitions/protocol.CredentialAssertion'
/api/webauthn/signin/finish:
post:
tags:
- Login API
description: WebAuthn Login Flow 2nd stage
operationId: ApiController.WebAuthnSigninBegin
parameters:
- in: body
name: body
description: authenticator assertion Response
required: true
schema:
$ref: '#/definitions/protocol.CredentialAssertionResponse'
responses:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
/api/webauthn/signup/begin:
get:
tags:
- User API
description: WebAuthn Registration Flow 1st stage
operationId: ApiController.WebAuthnSignupBegin
responses:
"200":
description: The CredentialCreationOptions object
schema:
$ref: '#/definitions/protocol.CredentialCreation'
/api/webauthn/signup/finish:
post:
tags:
- User API
description: WebAuthn Registration Flow 2nd stage
operationId: ApiController.WebAuthnSignupFinish
parameters:
- in: body
name: body
description: authenticator attestation Response
required: true
schema:
$ref: '#/definitions/protocol.CredentialCreationResponse'
responses:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
definitions: definitions:
2127.0xc000398090.false: 2127.0xc000427560.false:
title: "false" title: "false"
type: object type: object
2161.0xc0003980c0.false: 2161.0xc000427590.false:
title: "false" title: "false"
type: object type: object
Response: Response:
@@ -2113,9 +2192,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2127.0xc000398090.false' $ref: '#/definitions/2127.0xc000427560.false'
data2: data2:
$ref: '#/definitions/2161.0xc0003980c0.false' $ref: '#/definitions/2161.0xc000427590.false'
msg: msg:
type: string type: string
name: name:
@@ -2185,10 +2264,14 @@ definitions:
type: boolean type: boolean
enablePassword: enablePassword:
type: boolean type: boolean
enableSamlCompress:
type: boolean
enableSignUp: enableSignUp:
type: boolean type: boolean
enableSigninSession: enableSigninSession:
type: boolean type: boolean
enableWebAuthn:
type: boolean
expireInHours: expireInHours:
type: integer type: integer
format: int64 format: int64
@@ -2263,7 +2346,7 @@ definitions:
type: string type: string
privateKey: privateKey:
type: string type: string
publicKey: certificate:
type: string type: string
scope: scope:
type: string type: string
@@ -2977,6 +3060,10 @@ definitions:
type: string type: string
updatedTime: updatedTime:
type: string type: string
webauthnCredentials:
type: array
items:
$ref: '#/definitions/webauthn.Credential'
wechat: wechat:
type: string type: string
wecom: wecom:
@@ -3035,6 +3122,21 @@ definitions:
type: string type: string
url: url:
type: string type: string
protocol.CredentialAssertion:
title: CredentialAssertion
type: object
protocol.CredentialAssertionResponse:
title: CredentialAssertionResponse
type: object
protocol.CredentialCreation:
title: CredentialCreation
type: object
protocol.CredentialCreationResponse:
title: CredentialCreationResponse
type: object
webauthn.Credential:
title: Credential
type: object
xorm.Engine: xorm.Engine:
title: Engine title: Engine
type: object type: object

View File

@@ -17,7 +17,9 @@ package util
import ( import (
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha1"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex"
) )
func GetHmacSha1(keyStr, value string) string { func GetHmacSha1(keyStr, value string) string {
@@ -28,3 +30,10 @@ func GetHmacSha1(keyStr, value string) string {
return res return res
} }
func GetHmacSha256(key string, data string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(data))
return hex.EncodeToString(mac.Sum(nil))
}

View File

@@ -52,8 +52,10 @@ func ParseFloat(s string) float64 {
} }
func ParseBool(s string) bool { func ParseBool(s string) bool {
if s == "\x01" { if s == "\x01" || s == "true" {
return true return true
} else if s == "false" {
return false
} }
i := ParseInt(s) i := ParseInt(s)

1
web/.env Normal file
View File

@@ -0,0 +1 @@
PUBLIC_URL=.

View File

@@ -21,7 +21,7 @@
"rules": { "rules": {
// "eqeqeq": "error", // "eqeqeq": "error",
"semi": ["error", "always"], "semi": ["error", "always"],
// "indent": ["error", 2], "indent": ["error", 2],
// follow antd's style guide // follow antd's style guide
"quotes": ["error", "double"], "quotes": ["error", "double"],
"jsx-quotes": ["error", "prefer-double"], "jsx-quotes": ["error", "prefer-double"],
@@ -45,6 +45,23 @@
"curly": ["error", "all"], "curly": ["error", "all"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }], "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"no-mixed-spaces-and-tabs": "error", "no-mixed-spaces-and-tabs": "error",
"sort-imports": ["error", {
"ignoreDeclarationSort": true
}],
"no-multiple-empty-lines": ["error", { "max": 1, "maxBOF": 0, "maxEOF": 0 }],
"space-unary-ops": ["error", { "words": true, "nonwords": false }],
"space-infix-ops": "error",
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"comma-style": ["error", "last"],
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "never",
"exports": "never",
"functions": "never"
}],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"react/prop-types": "off", "react/prop-types": "off",
"react/display-name": "off", "react/display-name": "off",

View File

@@ -34,7 +34,7 @@ module.exports = {
"/cas/validate": { "/cas/validate": {
target: "http://localhost:8000", target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
} },
}, },
}, },
plugins: [ plugins: [

View File

@@ -39,7 +39,8 @@
"test": "craco test", "test": "craco test",
"eject": "craco eject", "eject": "craco eject",
"crowdin:sync": "crowdin upload && crowdin download", "crowdin:sync": "crowdin upload && crowdin download",
"preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Use yarn for installing: https://yarnpkg.com/en/docs/install')\"" "preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Use yarn for installing: https://yarnpkg.com/en/docs/install')\"",
"fix": "eslint --fix ."
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd"; import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -38,7 +38,7 @@ class AccountTable extends React.Component {
} }
addRow(table) { addRow(table) {
let row = {name: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true}; let row = {name: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true, viewRule: "Public", modifyRule: "Self"};
if (table === undefined) { if (table === undefined) {
table = []; table = [];
} }
@@ -86,6 +86,8 @@ class AccountTable extends React.Component {
{name: "Bio", displayName: i18next.t("user:Bio")}, {name: "Bio", displayName: i18next.t("user:Bio")},
{name: "Tag", displayName: i18next.t("user:Tag")}, {name: "Tag", displayName: i18next.t("user:Tag")},
{name: "Signup application", displayName: i18next.t("general:Signup application")}, {name: "Signup application", displayName: i18next.t("general:Signup application")},
{name: "Roles", displayName: i18next.t("general:Roles")},
{name: "Permissions", displayName: i18next.t("general:Permissions")},
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")}, {name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
{name: "Properties", displayName: i18next.t("user:Properties")}, {name: "Properties", displayName: i18next.t("user:Properties")},
{name: "Is admin", displayName: i18next.t("user:Is admin")}, {name: "Is admin", displayName: i18next.t("user:Is admin")},
@@ -114,7 +116,7 @@ class AccountTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("provider:visible"), title: i18next.t("provider:visible"),
@@ -127,7 +129,7 @@ class AccountTable extends React.Component {
this.updateField(table, index, "visible", checked); this.updateField(table, index, "visible", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("organization:viewRule"), title: i18next.t("organization:viewRule"),
@@ -154,7 +156,7 @@ class AccountTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("organization:modifyRule"), title: i18next.t("organization:modifyRule"),
@@ -189,7 +191,7 @@ class AccountTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -209,7 +211,7 @@ class AccountTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from "antd"; import {Avatar, BackTop, Button, Card, Dropdown, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom"; import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage"; import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage"; import OrganizationEditPage from "./OrganizationEditPage";
@@ -235,15 +235,19 @@ class App extends Component {
AuthBackend.logout() AuthBackend.logout()
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
const owner = this.state.account.owner;
this.setState({ this.setState({
account: null account: null,
}); });
Setting.showMessage("success", "Logged out successfully"); Setting.showMessage("success", "Logged out successfully");
let redirectUri = res.data2; let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
}else{ } else if (owner !== "built-in") {
Setting.goToLink(`${window.location.origin}/login/${owner}`);
} else {
Setting.goToLinkSoft(this, "/"); Setting.goToLinkSoft(this, "/");
} }
} else { } else {
@@ -254,7 +258,7 @@ class App extends Component {
onUpdateAccount(account) { onUpdateAccount(account) {
this.setState({ this.setState({
account: account account: account,
}); });
} }
@@ -562,7 +566,7 @@ class App extends Component {
renderContent() { renderContent() {
if (!Setting.isMobile()) { if (!Setting.isMobile()) {
return ( return (
<div style={{display: "flex", flex: "auto", width:"100%", flexDirection: "column"}}> <div style={{display: "flex", flex: "auto", width: "100%", flexDirection: "column"}}>
<Layout style={{display: "flex", alignItems: "stretch"}}> <Layout style={{display: "flex", alignItems: "stretch"}}>
<Header style={{padding: "0", marginBottom: "3px"}}> <Header style={{padding: "0", marginBottom: "3px"}}>
{ {
@@ -669,7 +673,9 @@ class App extends Component {
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} /> <Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} /> <Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} /> <Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} /> <Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />

View File

@@ -156,7 +156,7 @@ class ApplicationEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -181,7 +181,7 @@ class ApplicationEditPage extends React.Component {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} : {Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
</Col> </Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}> <Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
@@ -583,7 +583,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signup page URL")} {i18next.t("application:Copy signup page URL")}
</Button> </Button>
<br /> <br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}> <div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
{ {
this.state.application.enablePassword ? ( this.state.application.enablePassword ? (
<SignupPage application={this.state.application} /> <SignupPage application={this.state.application} />
@@ -603,7 +603,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signin page URL")} {i18next.t("application:Copy signin page URL")}
</Button> </Button>
<br /> <br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}> <div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} /> <LoginPage type={"login"} mode={"signin"} application={this.state.application} />
<div style={maskStyle}></div> <div style={maskStyle}></div>
</div> </div>

View File

@@ -99,7 +99,7 @@ class ApplicationListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -109,7 +109,7 @@ class ApplicationListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -130,7 +130,7 @@ class ApplicationListPage extends BaseListPage {
<img src={text} alt={text} width={150} /> <img src={text} alt={text} width={150} />
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
@@ -145,7 +145,7 @@ class ApplicationListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Providers"), title: i18next.t("general:Providers"),
@@ -222,7 +222,7 @@ class ApplicationListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -30,6 +30,7 @@ class BaseListPage extends React.Component {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }

View File

@@ -73,7 +73,7 @@ class CertEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -164,25 +164,25 @@ class CertEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col> </Col>
<Col span={9} > <Col span={9} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => { <Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.publicKey); copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully")); Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
}} }}
> >
{i18next.t("cert:Copy public key")} {i18next.t("cert:Copy certificate")}
</Button> </Button>
<Button type="primary" onClick={() => { <Button type="primary" onClick={() => {
const blob = new Blob([this.state.cert.publicKey], {type: "text/plain;charset=utf-8"}); const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.pem"); FileSaver.saveAs(blob, "token_jwt_key.pem");
}} }}
> >
{i18next.t("cert:Download public key")} {i18next.t("cert:Download certificate")}
</Button> </Button>
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => { <TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.certificate} onChange={e => {
this.updateCertField("publicKey", e.target.value); this.updateCertField("certificate", e.target.value);
}} /> }} />
</Col> </Col>
<Col span={1} /> <Col span={1} />

View File

@@ -34,7 +34,7 @@ class CertListPage extends BaseListPage {
cryptoAlgorithm: "RS256", cryptoAlgorithm: "RS256",
bitSize: 4096, bitSize: 4096,
expireInYears: 20, expireInYears: 20,
publicKey: "", certificate: "",
privateKey: "", privateKey: "",
}; };
} }
@@ -82,7 +82,7 @@ class CertListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -92,7 +92,7 @@ class CertListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -169,7 +169,7 @@ class CertListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -16,7 +16,7 @@ import React, {useState} from "react";
import Cropper from "react-cropper"; import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css"; import "cropperjs/dist/cropper.css";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {Button, Row, Col, Modal} from "antd"; import {Button, Col, Modal, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import * as ResourceBackend from "./backend/ResourceBackend"; import * as ResourceBackend from "./backend/ResourceBackend";

View File

@@ -42,7 +42,7 @@ class LdapEditPage extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.setState({ this.setState({
ldap: res.data ldap: res.data,
}); });
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
@@ -71,7 +71,7 @@ class LdapEditPage extends React.Component {
return ( return (
<span style={{ <span style={{
color: "#faad14", color: "#faad14",
marginLeft: "20px" marginLeft: "20px",
}}>{i18next.t("ldap:The Auto Sync option will sync all users to specify organization")}</span> }}>{i18next.t("ldap:The Auto Sync option will sync all users to specify organization")}</span>
); );
} }

View File

@@ -23,7 +23,7 @@ class LdapListPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
ldaps: null ldaps: null,
}; };
} }
@@ -65,7 +65,7 @@ class LdapListPage extends React.Component {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
@@ -79,7 +79,7 @@ class LdapListPage extends React.Component {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("ldap:Server"), title: i18next.t("ldap:Server"),
@@ -89,7 +89,7 @@ class LdapListPage extends React.Component {
sorter: (a, b) => a.host.localeCompare(b.host), sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => { render: (text, record, index) => {
return `${text}:${record.port}`; return `${text}:${record.port}`;
} },
}, },
{ {
title: i18next.t("ldap:Base DN"), title: i18next.t("ldap:Base DN"),
@@ -114,7 +114,7 @@ class LdapListPage extends React.Component {
render: (text, record, index) => { render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : ( return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>); <span style={{color: "#52c41a"}}>{text + " mins"}</span>);
} },
}, },
{ {
title: i18next.t("ldap:Last Sync"), title: i18next.t("ldap:Last Sync"),
@@ -124,7 +124,7 @@ class LdapListPage extends React.Component {
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync), sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => { render: (text, record, index) => {
return text; return text;
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -148,7 +148,7 @@ class LdapListPage extends React.Component {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Row, Table, Popconfirm} from "antd"; import {Button, Col, Popconfirm, Row, Table} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend"; import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next"; import i18next from "i18next";
@@ -87,7 +87,6 @@ class LdapSyncPage extends React.Component {
}); });
} }
getLdapUser(ldap) { getLdapUser(ldap) {
LdapBackend.getLdapUser(ldap) LdapBackend.getLdapUser(ldap)
.then((res) => { .then((res) => {

View File

@@ -48,7 +48,7 @@ class LdapTable extends React.Component {
passwd: "123", passwd: "123",
baseDn: "ou=People,dc=example,dc=com", baseDn: "ou=People,dc=example,dc=com",
autosync: 0, autosync: 0,
lastSync: "" lastSync: "",
}; };
} }
@@ -104,7 +104,7 @@ class LdapTable extends React.Component {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("ldap:Server"), title: i18next.t("ldap:Server"),
@@ -114,7 +114,7 @@ class LdapTable extends React.Component {
sorter: (a, b) => a.host.localeCompare(b.host), sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => { render: (text, record, index) => {
return `${text}:${record.port}`; return `${text}:${record.port}`;
} },
}, },
{ {
title: i18next.t("ldap:Base DN"), title: i18next.t("ldap:Base DN"),
@@ -132,7 +132,7 @@ class LdapTable extends React.Component {
render: (text, record, index) => { render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : ( return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>); <span style={{color: "#52c41a"}}>{text + " mins"}</span>);
} },
}, },
{ {
title: i18next.t("ldap:Last Sync"), title: i18next.t("ldap:Last Sync"),
@@ -142,7 +142,7 @@ class LdapTable extends React.Component {
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync), sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => { render: (text, record, index) => {
return text; return text;
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -166,7 +166,7 @@ class LdapTable extends React.Component {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -97,7 +97,7 @@ class ModelEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -76,7 +76,7 @@ class ModelListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -92,7 +92,7 @@ class ModelListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -102,7 +102,7 @@ class ModelListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -122,7 +122,7 @@ class ModelListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -143,7 +143,7 @@ class ModelListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -60,7 +60,7 @@ class OrganizationEditPage extends React.Component {
} }
} }
this.setState({ this.setState({
ldaps: resdata ldaps: resdata,
}); });
}); });
} }
@@ -91,7 +91,7 @@ class OrganizationEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :

View File

@@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd"; import {Button, Popconfirm, Result, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -57,6 +57,8 @@ class OrganizationListPage extends BaseListPage {
{name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"}, {name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"}, {name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
@@ -110,7 +112,7 @@ class OrganizationListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -120,7 +122,7 @@ class OrganizationListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -141,7 +143,7 @@ class OrganizationListPage extends BaseListPage {
<img src={text} alt={text} width={40} /> <img src={text} alt={text} width={40} />
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("organization:Website URL"), title: i18next.t("organization:Website URL"),
@@ -156,7 +158,7 @@ class OrganizationListPage extends BaseListPage {
{text} {text}
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("general:Password type"), title: i18next.t("general:Password type"),
@@ -190,7 +192,7 @@ class OrganizationListPage extends BaseListPage {
<img src={text} alt={text} width={40} /> <img src={text} alt={text} width={40} />
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("organization:Soft deletion"), title: i18next.t("organization:Soft deletion"),
@@ -202,7 +204,7 @@ class OrganizationListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -224,7 +226,7 @@ class OrganizationListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];
@@ -235,6 +237,17 @@ class OrganizationListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
}; };
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
@@ -272,6 +285,13 @@ class OrganizationListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Modal, Row, Input} from "antd"; import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";

View File

@@ -152,7 +152,7 @@ class PaymentEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -89,7 +89,7 @@ class PaymentListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
@@ -104,7 +104,7 @@ class PaymentListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -120,7 +120,7 @@ class PaymentListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -130,7 +130,7 @@ class PaymentListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
// { // {
// title: i18next.t("general:Display name"), // title: i18next.t("general:Display name"),
@@ -154,7 +154,7 @@ class PaymentListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("payment:Type"), title: i18next.t("payment:Type"),
@@ -163,12 +163,12 @@ class PaymentListPage extends BaseListPage {
width: "140px", width: "140px",
align: "center", align: "center",
filterMultiple: false, filterMultiple: false,
filters: Setting.getProviderTypeOptions("Payment").map((o) => {return {text:o.id, value:o.name};}), filters: Setting.getProviderTypeOptions("Payment").map((o) => {return {text: o.id, value: o.name};}),
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
record.category = "Payment"; record.category = "Payment";
return Provider.getProviderLogoWidget(record); return Provider.getProviderLogoWidget(record);
} },
}, },
{ {
title: i18next.t("payment:Product"), title: i18next.t("payment:Product"),
@@ -221,7 +221,7 @@ class PaymentListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -67,7 +67,7 @@ class PaymentResultPage extends React.Component {
Setting.goToLink(payment.returnUrl); Setting.goToLink(payment.returnUrl);
}}> }}>
{i18next.t("payment:Return to Website")} {i18next.t("payment:Return to Website")}
</Button> </Button>,
]} ]}
/> />
</div> </div>
@@ -103,7 +103,7 @@ class PaymentResultPage extends React.Component {
Setting.goToLink(payment.returnUrl); Setting.goToLink(payment.returnUrl);
}}> }}>
{i18next.t("payment:Return to Website")} {i18next.t("payment:Return to Website")}
</Button> </Button>,
]} ]}
/> />
</div> </div>

View File

@@ -132,7 +132,7 @@ class PermissionEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
@@ -143,6 +143,8 @@ class PermissionEditPage extends React.Component {
this.getUsers(owner); this.getUsers(owner);
this.getRoles(owner); this.getRoles(owner);
this.getModels(owner);
this.getResources(owner);
})}> })}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)

View File

@@ -33,7 +33,7 @@ class PermissionListPage extends BaseListPage {
roles: [], roles: [],
resourceType: "Application", resourceType: "Application",
resources: ["app-built-in"], resources: ["app-built-in"],
action: "Read", actions: ["Read"],
effect: "Allow", effect: "Allow",
isEnabled: true, isEnabled: true,
}; };
@@ -81,7 +81,7 @@ class PermissionListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -97,7 +97,7 @@ class PermissionListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -107,7 +107,7 @@ class PermissionListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -126,7 +126,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("users"), ...this.getColumnSearchProps("users"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("role:Sub roles"), title: i18next.t("role:Sub roles"),
@@ -137,7 +137,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("roles"), ...this.getColumnSearchProps("roles"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("permission:Resource type"), title: i18next.t("permission:Resource type"),
@@ -159,7 +159,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("resources"), ...this.getColumnSearchProps("resources"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("permission:Actions"), title: i18next.t("permission:Actions"),
@@ -170,7 +170,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("actions"), ...this.getColumnSearchProps("actions"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("permission:Effect"), title: i18next.t("permission:Effect"),
@@ -194,7 +194,7 @@ class PermissionListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -214,7 +214,7 @@ class PermissionListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -85,7 +85,7 @@ class ProductEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -110,7 +110,7 @@ class ProductEditPage extends React.Component {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} : {Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
</Col> </Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}> <Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :

View File

@@ -84,7 +84,7 @@ class ProductListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -94,7 +94,7 @@ class ProductListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -115,7 +115,7 @@ class ProductListPage extends BaseListPage {
<img src={text} alt={text} width={150} /> <img src={text} alt={text} width={150} />
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("product:Tag"), title: i18next.t("product:Tag"),
@@ -240,7 +240,7 @@ class ProductListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -167,7 +167,7 @@ class ProviderEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -208,6 +208,8 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("domain", Setting.getFullServerUrl()); this.updateProviderField("domain", Setting.getFullServerUrl());
} else if (value === "SAML") { } else if (value === "SAML") {
this.updateProviderField("type", "Aliyun IDaaS"); this.updateProviderField("type", "Aliyun IDaaS");
} else if (value === "Payment") {
this.updateProviderField("type", "Alipay");
} else if (value === "Captcha") { } else if (value === "Captcha") {
this.updateProviderField("type", "Default"); this.updateProviderField("type", "Default");
} }
@@ -267,7 +269,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
{ {
this.state.provider.type !== "WeCom" ? null : ( this.state.provider.type !== "WeCom" ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
@@ -416,7 +418,7 @@ class ProviderEditPage extends React.Component {
) )
} }
{ {
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : ( this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@@ -622,7 +624,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP public key"))} : {Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP certificate"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.idP} onChange={e => { <Input value={this.state.provider.idP} onChange={e => {

View File

@@ -85,7 +85,7 @@ class ProviderListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -95,7 +95,7 @@ class ProviderListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -128,17 +128,17 @@ class ProviderListPage extends BaseListPage {
align: "center", align: "center",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: "OAuth", value: "OAuth", children: Setting.getProviderTypeOptions("OAuth").map((o) => {return {text:o.id, value:o.name};})}, {text: "OAuth", value: "OAuth", children: Setting.getProviderTypeOptions("OAuth").map((o) => {return {text: o.id, value: o.name};})},
{text: "Email", value: "Email", children: Setting.getProviderTypeOptions("Email").map((o) => {return {text:o.id, value:o.name};})}, {text: "Email", value: "Email", children: Setting.getProviderTypeOptions("Email").map((o) => {return {text: o.id, value: o.name};})},
{text: "SMS", value: "SMS", children: Setting.getProviderTypeOptions("SMS").map((o) => {return {text:o.id, value:o.name};})}, {text: "SMS", value: "SMS", children: Setting.getProviderTypeOptions("SMS").map((o) => {return {text: o.id, value: o.name};})},
{text: "Storage", value: "Storage", children: Setting.getProviderTypeOptions("Storage").map((o) => {return {text:o.id, value:o.name};})}, {text: "Storage", value: "Storage", children: Setting.getProviderTypeOptions("Storage").map((o) => {return {text: o.id, value: o.name};})},
{text: "SAML", value: "SAML", children: Setting.getProviderTypeOptions("SAML").map((o) => {return {text:o.id, value:o.name};})}, {text: "SAML", value: "SAML", children: Setting.getProviderTypeOptions("SAML").map((o) => {return {text: o.id, value: o.name};})},
{text: "Captcha", value: "Captcha", children: Setting.getProviderTypeOptions("Captcha").map((o) => {return {text:o.id, value:o.name};})}, {text: "Captcha", value: "Captcha", children: Setting.getProviderTypeOptions("Captcha").map((o) => {return {text: o.id, value: o.name};})},
], ],
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Provider.getProviderLogoWidget(record); return Provider.getProviderLogoWidget(record);
} },
}, },
{ {
title: i18next.t("provider:Client ID"), title: i18next.t("provider:Client ID"),
@@ -149,7 +149,7 @@ class ProviderListPage extends BaseListPage {
...this.getColumnSearchProps("clientId"), ...this.getColumnSearchProps("clientId"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getShortText(text); return Setting.getShortText(text);
} },
}, },
{ {
title: i18next.t("provider:Provider URL"), title: i18next.t("provider:Provider URL"),
@@ -166,7 +166,7 @@ class ProviderListPage extends BaseListPage {
} }
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -186,7 +186,7 @@ class ProviderListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd"; import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -82,7 +82,7 @@ class ProviderTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("provider:Category"), title: i18next.t("provider:Category"),
@@ -92,7 +92,7 @@ class ProviderTable extends React.Component {
render: (text, record, index) => { render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name); const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return provider?.category; return provider?.category;
} },
}, },
{ {
title: i18next.t("provider:Type"), title: i18next.t("provider:Type"),
@@ -102,7 +102,7 @@ class ProviderTable extends React.Component {
render: (text, record, index) => { render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name); const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return Provider.getProviderLogoWidget(provider); return Provider.getProviderLogoWidget(provider);
} },
}, },
{ {
title: i18next.t("provider:canSignUp"), title: i18next.t("provider:canSignUp"),
@@ -119,7 +119,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "canSignUp", checked); this.updateField(table, index, "canSignUp", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("provider:canSignIn"), title: i18next.t("provider:canSignIn"),
@@ -136,7 +136,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "canSignIn", checked); this.updateField(table, index, "canSignIn", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("provider:canUnlink"), title: i18next.t("provider:canUnlink"),
@@ -153,7 +153,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "canUnlink", checked); this.updateField(table, index, "canUnlink", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("provider:prompted"), title: i18next.t("provider:prompted"),
@@ -170,7 +170,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "prompted", checked); this.updateField(table, index, "prompted", checked);
}} /> }} />
); );
} },
}, },
// { // {
// title: i18next.t("provider:alertType"), // title: i18next.t("provider:alertType"),
@@ -211,7 +211,7 @@ class ProviderTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -32,7 +32,7 @@ class RecordListPage extends BaseListPage {
return { return {
owner: "built-in", owner: "built-in",
name: "1234", name: "1234",
id : "1234", id: "1234",
clientIp: "::1", clientIp: "::1",
timestamp: moment().format(), timestamp: moment().format(),
organization: "built-in", organization: "built-in",
@@ -74,7 +74,7 @@ class RecordListPage extends BaseListPage {
{text} {text}
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("general:Timestamp"), title: i18next.t("general:Timestamp"),
@@ -84,7 +84,7 @@ class RecordListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
@@ -99,7 +99,7 @@ class RecordListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
@@ -114,7 +114,7 @@ class RecordListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Method"), title: i18next.t("general:Method"),
@@ -153,7 +153,7 @@ class RecordListPage extends BaseListPage {
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return text; return text;
} },
}, },
{ {
title: i18next.t("record:Is Triggered"), title: i18next.t("record:Is Triggered"),
@@ -170,7 +170,7 @@ class RecordListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
]; ];

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Modal, Row, Input} from "antd"; import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";

View File

@@ -99,7 +99,7 @@ class ResourceListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("resource:Application"), title: i18next.t("resource:Application"),
@@ -114,7 +114,7 @@ class ResourceListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("resource:User"), title: i18next.t("resource:User"),
@@ -129,7 +129,7 @@ class ResourceListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("resource:Parent"), title: i18next.t("resource:Parent"),
@@ -155,7 +155,7 @@ class ResourceListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("resource:Tag"), title: i18next.t("resource:Tag"),
@@ -196,7 +196,7 @@ class ResourceListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFriendlyFileSize(text); return Setting.getFriendlyFileSize(text);
} },
}, },
{ {
title: i18next.t("general:Preview"), title: i18next.t("general:Preview"),
@@ -219,7 +219,7 @@ class ResourceListPage extends BaseListPage {
</div> </div>
); );
} }
} },
}, },
{ {
title: i18next.t("general:URL"), title: i18next.t("general:URL"),
@@ -238,7 +238,7 @@ class ResourceListPage extends BaseListPage {
</Button> </Button>
</div> </div>
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -260,7 +260,7 @@ class ResourceListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -107,7 +107,7 @@ class RoleEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -77,7 +77,7 @@ class RoleListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -93,7 +93,7 @@ class RoleListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -103,7 +103,7 @@ class RoleListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -122,7 +122,7 @@ class RoleListPage extends BaseListPage {
...this.getColumnSearchProps("users"), ...this.getColumnSearchProps("users"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("role:Sub roles"), title: i18next.t("role:Sub roles"),
@@ -133,7 +133,7 @@ class RoleListPage extends BaseListPage {
...this.getColumnSearchProps("roles"), ...this.getColumnSearchProps("roles"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
@@ -145,7 +145,7 @@ class RoleListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -165,7 +165,7 @@ class RoleListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {Menu, Dropdown} from "antd"; import {Dropdown, Menu} from "antd";
import {createFromIconfontCN} from "@ant-design/icons"; import {createFromIconfontCN} from "@ant-design/icons";
import "./App.less"; import "./App.less";

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {message, Tag, Tooltip} from "antd"; import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons"; import {QuestionCircleTwoTone} from "@ant-design/icons";
import React from "react"; import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
@@ -75,35 +75,35 @@ export const OtherProviderInfo = {
}, },
"Azure Blob": { "Azure Blob": {
logo: `${StaticBaseUrl}/img/social_azure.jpg`, logo: `${StaticBaseUrl}/img/social_azure.jpg`,
url: "https://azure.microsoft.com/en-us/services/storage/blobs/" url: "https://azure.microsoft.com/en-us/services/storage/blobs/",
} },
}, },
SAML: { SAML: {
"Aliyun IDaaS": { "Aliyun IDaaS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`, logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/idaas" url: "https://aliyun.com/product/idaas",
}, },
"Keycloak": { "Keycloak": {
logo: `${StaticBaseUrl}/img/social_keycloak.png`, logo: `${StaticBaseUrl}/img/social_keycloak.png`,
url: "https://www.keycloak.org/" url: "https://www.keycloak.org/",
}, },
}, },
Payment: { Payment: {
"Alipay": { "Alipay": {
logo: `${StaticBaseUrl}/img/payment_alipay.png`, logo: `${StaticBaseUrl}/img/payment_alipay.png`,
url: "https://www.alipay.com/" url: "https://www.alipay.com/",
}, },
"WeChat Pay": { "WeChat Pay": {
logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`, logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`,
url: "https://pay.weixin.qq.com/" url: "https://pay.weixin.qq.com/",
}, },
"PayPal": { "PayPal": {
logo: `${StaticBaseUrl}/img/payment_paypal.png`, logo: `${StaticBaseUrl}/img/payment_paypal.png`,
url: "https://www.paypal.com/" url: "https://www.paypal.com/",
}, },
"GC": { "GC": {
logo: `${StaticBaseUrl}/img/payment_gc.png`, logo: `${StaticBaseUrl}/img/payment_gc.png`,
url: "https://gc.org" url: "https://gc.org",
}, },
}, },
Captcha: { Captcha: {
@@ -122,8 +122,12 @@ export const OtherProviderInfo = {
"Aliyun Captcha": { "Aliyun Captcha": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`, logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://help.aliyun.com/product/28308.html", url: "https://help.aliyun.com/product/28308.html",
} },
} "GEETEST": {
logo: `${StaticBaseUrl}/img/social_geetest.png`,
url: "https://www.geetest.com",
},
},
}; };
export function getCountryRegionData() { export function getCountryRegionData() {
@@ -136,7 +140,7 @@ export function getCountryRegionData() {
countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json")); countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
var data = countries.getNames(language, {select: "official"}); var data = countries.getNames(language, {select: "official"});
var result = []; var result = [];
for (var i in data) {result.push({code:i, name:data[i]});} for (var i in data) {result.push({code: i, name: data[i]});}
return result; return result;
} }
@@ -301,7 +305,7 @@ export function isPromptAnswered(user, application) {
} }
const providerItems = getAllPromptedProviderItems(application); const providerItems = getAllPromptedProviderItems(application);
for (let i = 0; i < providerItems.length; i ++) { for (let i = 0; i < providerItems.length; i++) {
if (!isProviderItemAnswered(user, application, providerItems[i])) { if (!isProviderItemAnswered(user, application, providerItems[i])) {
return false; return false;
} }
@@ -428,7 +432,7 @@ export function getShortName(s) {
return s.split("/").slice(-1)[0]; return s.split("/").slice(-1)[0];
} }
export function getShortText(s, maxLength=35) { export function getShortText(s, maxLength = 35) {
if (s.length > maxLength) { if (s.length > maxLength) {
return `${s.slice(0, maxLength)}...`; return `${s.slice(0, maxLength)}...`;
} else { } else {
@@ -445,13 +449,13 @@ export function getFriendlyFileSize(size) {
let num = (size / Math.pow(1024, i)); let num = (size / Math.pow(1024, i));
let round = Math.round(num); let round = Math.round(num);
num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round; num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round;
return `${num} ${"KMGTPEZY"[i-1]}B`; return `${num} ${"KMGTPEZY"[i - 1]}B`;
} }
function getRandomInt(s) { function getRandomInt(s) {
let hash = 0; let hash = 0;
if (s.length !== 0) { if (s.length !== 0) {
for (let i = 0; i < s.length; i ++) { for (let i = 0; i < s.length; i++) {
let char = s.charCodeAt(i); let char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char; hash = ((hash << 5) - hash) + char;
hash = hash & hash; hash = hash & hash;
@@ -594,7 +598,7 @@ export function getProviderTypeOptions(category) {
{id: "AWS S3", name: "AWS S3"}, {id: "AWS S3", name: "AWS S3"},
{id: "Aliyun OSS", name: "Aliyun OSS"}, {id: "Aliyun OSS", name: "Aliyun OSS"},
{id: "Tencent Cloud COS", name: "Tencent Cloud COS"}, {id: "Tencent Cloud COS", name: "Tencent Cloud COS"},
{id: "Azure Blob", name: "Azure Blob"} {id: "Azure Blob", name: "Azure Blob"},
] ]
); );
} else if (category === "SAML") { } else if (category === "SAML") {
@@ -615,6 +619,7 @@ export function getProviderTypeOptions(category) {
{id: "reCAPTCHA", name: "reCAPTCHA"}, {id: "reCAPTCHA", name: "reCAPTCHA"},
{id: "hCaptcha", name: "hCaptcha"}, {id: "hCaptcha", name: "hCaptcha"},
{id: "Aliyun Captcha", name: "Aliyun Captcha"}, {id: "Aliyun Captcha", name: "Aliyun Captcha"},
{id: "GEETEST", name: "GEETEST"},
]); ]);
} else { } else {
return []; return [];
@@ -662,8 +667,8 @@ export function goToLogin(ths, application) {
return; return;
} }
if (!application.enablePassword && window.location.pathname.includes("/signup/oauth/authorize")) { if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
const link = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize"); const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
goToLink(link); goToLink(link);
return; return;
} }
@@ -685,7 +690,7 @@ export function goToSignup(ths, application) {
} }
if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) { if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) {
const link = window.location.href.replace("/login/oauth/authorize", "/signup/oauth/authorize"); const link = window.location.href.replace("/login/oauth/authorize", "/auto-signup/oauth/authorize");
goToLink(link); goToLink(link);
return; return;
} }
@@ -718,7 +723,7 @@ export function goToForget(ths, application) {
} }
export function renderHelmet(application) { export function renderHelmet(application) {
if (application === undefined || application === null || application.organizationObj === undefined || application.organizationObj === null ||application.organizationObj === "") { if (application === undefined || application === null || application.organizationObj === undefined || application.organizationObj === null || application.organizationObj === "") {
return null; return null;
} }
@@ -786,7 +791,7 @@ export function getDeduplicatedArray(array, filterArray, key) {
export function getNewRowNameForTable(table, rowName) { export function getNewRowNameForTable(table, rowName) {
const emptyCount = table.filter(row => row.name.includes(rowName)).length; const emptyCount = table.filter(row => row.name.includes(rowName)).length;
let res = rowName; let res = rowName;
for (let i = 0; i < emptyCount; i ++) { for (let i = 0; i < emptyCount; i++) {
res = res + " "; res = res + " ";
} }
return res; return res;
@@ -847,86 +852,86 @@ export function getSyncerTableColumns(syncer) {
case "Keycloak": case "Keycloak":
return [ return [
{ {
"name":"ID", "name": "ID",
"type":"string", "type": "string",
"casdoorName":"Id", "casdoorName": "Id",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"USERNAME", "name": "USERNAME",
"type":"string", "type": "string",
"casdoorName":"Name", "casdoorName": "Name",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"LAST_NAME+FIRST_NAME", "name": "LAST_NAME+FIRST_NAME",
"type":"string", "type": "string",
"casdoorName":"DisplayName", "casdoorName": "DisplayName",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"EMAIL", "name": "EMAIL",
"type":"string", "type": "string",
"casdoorName":"Email", "casdoorName": "Email",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"EMAIL_VERIFIED", "name": "EMAIL_VERIFIED",
"type":"boolean", "type": "boolean",
"casdoorName":"EmailVerified", "casdoorName": "EmailVerified",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"FIRST_NAME", "name": "FIRST_NAME",
"type":"string", "type": "string",
"casdoorName":"FirstName", "casdoorName": "FirstName",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"LAST_NAME", "name": "LAST_NAME",
"type":"string", "type": "string",
"casdoorName":"LastName", "casdoorName": "LastName",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"CREATED_TIMESTAMP", "name": "CREATED_TIMESTAMP",
"type":"string", "type": "string",
"casdoorName":"CreatedTime", "casdoorName": "CreatedTime",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
}, },
{ {
"name":"ENABLED", "name": "ENABLED",
"type":"boolean", "type": "boolean",
"casdoorName":"IsForbidden", "casdoorName": "IsForbidden",
"isHashed":true, "isHashed": true,
"values":[ "values": [
] ],
} },
]; ];
default: default:
return []; return [];

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd"; import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -101,7 +101,7 @@ class SignupTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("provider:visible"), title: i18next.t("provider:visible"),
@@ -123,7 +123,7 @@ class SignupTable extends React.Component {
} }
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("provider:required"), title: i18next.t("provider:required"),
@@ -140,7 +140,7 @@ class SignupTable extends React.Component {
this.updateField(table, index, "required", checked); this.updateField(table, index, "required", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("provider:prompted"), title: i18next.t("provider:prompted"),
@@ -161,7 +161,7 @@ class SignupTable extends React.Component {
this.updateField(table, index, "prompted", checked); this.updateField(table, index, "prompted", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("application:rule"), title: i18next.t("application:rule"),
@@ -201,7 +201,7 @@ class SignupTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -221,7 +221,7 @@ class SignupTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -89,7 +89,7 @@ class SyncerEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -101,7 +101,7 @@ class SyncerListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -117,7 +117,7 @@ class SyncerListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -127,7 +127,7 @@ class SyncerListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("provider:Type"), title: i18next.t("provider:Type"),
@@ -212,7 +212,7 @@ class SyncerListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -233,7 +233,7 @@ class SyncerListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd"; import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -73,7 +73,7 @@ class SyncerTableColumnTable extends React.Component {
this.updateField(table, index, "name", e.target.value); this.updateField(table, index, "name", e.target.value);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("syncer:Column type"), title: i18next.t("syncer:Column type"),
@@ -88,7 +88,7 @@ class SyncerTableColumnTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("syncer:Casdoor column"), title: i18next.t("syncer:Casdoor column"),
@@ -105,7 +105,7 @@ class SyncerTableColumnTable extends React.Component {
} }
</Select> </Select>
); );
} },
}, },
{ {
title: i18next.t("syncer:Is hashed"), title: i18next.t("syncer:Is hashed"),
@@ -117,7 +117,7 @@ class SyncerTableColumnTable extends React.Component {
this.updateField(table, index, "isHashed", checked); this.updateField(table, index, "isHashed", checked);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -137,7 +137,7 @@ class SyncerTableColumnTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -54,6 +54,6 @@ function testEmailProvider(provider, email = "") {
return fetch(`${Setting.ServerUrl}/api/send-email`, { return fetch(`${Setting.ServerUrl}/api/send-email`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(emailForm) body: JSON.stringify(emailForm),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@@ -68,7 +68,7 @@ class TokenEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}: {i18next.t("general:Name")}:

View File

@@ -81,7 +81,7 @@ class TokenListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -91,7 +91,7 @@ class TokenListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Application"), title: i18next.t("general:Application"),
@@ -106,7 +106,7 @@ class TokenListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
@@ -121,7 +121,7 @@ class TokenListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
@@ -136,7 +136,7 @@ class TokenListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("token:Authorization code"), title: i18next.t("token:Authorization code"),
@@ -147,7 +147,7 @@ class TokenListPage extends BaseListPage {
...this.getColumnSearchProps("code"), ...this.getColumnSearchProps("code"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getClickable(text); return Setting.getClickable(text);
} },
}, },
{ {
title: i18next.t("token:Access token"), title: i18next.t("token:Access token"),
@@ -159,7 +159,7 @@ class TokenListPage extends BaseListPage {
...this.getColumnSearchProps("accessToken"), ...this.getColumnSearchProps("accessToken"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getClickable(text); return Setting.getClickable(text);
} },
}, },
{ {
title: i18next.t("token:Expires in"), title: i18next.t("token:Expires in"),
@@ -202,7 +202,7 @@ class TokenListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined, LinkOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, LinkOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from "antd"; import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -71,7 +71,7 @@ class UrlTable extends React.Component {
this.updateField(table, index, e.target.value); this.updateField(table, index, e.target.value);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -91,7 +91,7 @@ class UrlTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -291,7 +291,7 @@ class UserEditPage extends React.Component {
}} /> }} />
</Col> </Col>
<Col span={11} > <Col span={11} >
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null} {this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
</Col> </Col>
</Row> </Row>
); );
@@ -309,7 +309,7 @@ class UserEditPage extends React.Component {
}} /> }} />
</Col> </Col>
<Col span={11} > <Col span={11} >
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null} {this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
</Col> </Col>
</Row> </Row>
); );
@@ -427,6 +427,32 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
); );
} else if (accountItem.name === "Roles") {
return (
<Row style={{marginTop: "20px", alignItems: "center"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Roles"), i18next.t("general:Roles - Tooltip"))} :
</Col>
<Col span={22} >
{
Setting.getTags(this.state.user.roles.map(role => role.name))
}
</Col>
</Row>
);
} else if (accountItem.name === "Permissions") {
return (
<Row style={{marginTop: "20px", alignItems: "center"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Permissions"), i18next.t("general:Permissions - Tooltip"))} :
</Col>
<Col span={22} >
{
Setting.getTags(this.state.user.permissions.map(permission => permission.name))
}
</Col>
</Row>
);
} else if (accountItem.name === "3rd-party logins") { } else if (accountItem.name === "3rd-party logins") {
return ( return (
!this.isSelfOrAdmin() ? null : ( !this.isSelfOrAdmin() ? null : (
@@ -440,7 +466,7 @@ class UserEditPage extends React.Component {
(this.state.application === null || this.state.user === null) ? null : ( (this.state.application === null || this.state.user === null) ? null : (
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) => this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
(providerItem.provider.category === "OAuth") ? ( (providerItem.provider.category === "OAuth") ? (
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} /> <OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} account={this.props.account} onUnlinked={() => {return this.unlinked();}} />
) : ( ) : (
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} /> <SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />
) )
@@ -541,7 +567,7 @@ class UserEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
{ {
this.state.application?.organizationObj.accountItems?.map(accountItem => { this.state.application?.organizationObj.accountItems?.map(accountItem => {
return ( return (

View File

@@ -152,7 +152,7 @@ class UserListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Application"), title: i18next.t("general:Application"),
@@ -168,7 +168,7 @@ class UserListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -184,7 +184,7 @@ class UserListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -194,7 +194,7 @@ class UserListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
@@ -215,7 +215,7 @@ class UserListPage extends BaseListPage {
<img src={text} alt={text} width={50} /> <img src={text} alt={text} width={50} />
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("general:Email"), title: i18next.t("general:Email"),
@@ -230,7 +230,7 @@ class UserListPage extends BaseListPage {
{text} {text}
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("general:Phone"), title: i18next.t("general:Phone"),
@@ -281,7 +281,7 @@ class UserListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("user:Is global admin"), title: i18next.t("user:Is global admin"),
@@ -293,7 +293,7 @@ class UserListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("user:Is forbidden"), title: i18next.t("user:Is forbidden"),
@@ -305,7 +305,7 @@ class UserListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("user:Is deleted"), title: i18next.t("user:Is deleted"),
@@ -317,7 +317,7 @@ class UserListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -337,7 +337,7 @@ class UserListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -35,8 +35,8 @@ class WebAuthnCredentialTable extends React.Component {
{i18next.t("general:Delete")} {i18next.t("general:Delete")}
</Button> </Button>
); );
} },
} },
]; ];
return ( return (

View File

@@ -84,8 +84,8 @@ const userTemplate = {
"phoneVerifiedTime": "", "phoneVerifiedTime": "",
"renameQuota": "3", "renameQuota": "3",
"tagline": "", "tagline": "",
"website": "" "website": "",
} },
}; };
class WebhookEditPage extends React.Component { class WebhookEditPage extends React.Component {
@@ -155,7 +155,7 @@ class WebhookEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner"> } style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from "antd"; import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -72,7 +72,7 @@ class WebhookHeaderTable extends React.Component {
this.updateField(table, index, "name", e.target.value); this.updateField(table, index, "name", e.target.value);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("webhook:Value"), title: i18next.t("webhook:Value"),
@@ -84,7 +84,7 @@ class WebhookHeaderTable extends React.Component {
this.updateField(table, index, "value", e.target.value); this.updateField(table, index, "value", e.target.value);
}} /> }} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -104,7 +104,7 @@ class WebhookHeaderTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -80,7 +80,7 @@ class WebhookListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
@@ -96,7 +96,7 @@ class WebhookListPage extends BaseListPage {
{text} {text}
</Link> </Link>
); );
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
@@ -106,7 +106,7 @@ class WebhookListPage extends BaseListPage {
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("webhook:URL"), title: i18next.t("webhook:URL"),
@@ -123,7 +123,7 @@ class WebhookListPage extends BaseListPage {
} }
</a> </a>
); );
} },
}, },
{ {
title: i18next.t("webhook:Method"), title: i18next.t("webhook:Method"),
@@ -143,7 +143,7 @@ class WebhookListPage extends BaseListPage {
filters: [ filters: [
{text: "application/json", value: "application/json"}, {text: "application/json", value: "application/json"},
{text: "application/x-www-form-urlencoded", value: "application/x-www-form-urlencoded"}, {text: "application/x-www-form-urlencoded", value: "application/x-www-form-urlencoded"},
] ],
}, },
{ {
title: i18next.t("webhook:Events"), title: i18next.t("webhook:Events"),
@@ -154,7 +154,7 @@ class WebhookListPage extends BaseListPage {
...this.getColumnSearchProps("events"), ...this.getColumnSearchProps("events"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("webhook:Is user extended"), title: i18next.t("webhook:Is user extended"),
@@ -166,7 +166,7 @@ class WebhookListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
@@ -178,7 +178,7 @@ class WebhookListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
); );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
@@ -198,7 +198,7 @@ class WebhookListPage extends BaseListPage {
</Popconfirm> </Popconfirm>
</div> </div>
); );
} },
}, },
]; ];

View File

@@ -17,7 +17,7 @@ import {authConfig} from "./Auth";
export function getAccount(query) { export function getAccount(query) {
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, { return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@@ -62,7 +62,7 @@ class ForgetPage extends React.Component {
} else { } else {
Util.showMessage( Util.showMessage(
"error", "error",
i18next.t("forget:Unknown forgot type: ") + this.state.type i18next.t("forget:Unknown forget type: ") + this.state.type
); );
} }
} }
@@ -96,7 +96,7 @@ class ForgetPage extends React.Component {
AuthBackend.getEmailAndPhone({ AuthBackend.getEmailAndPhone({
application: forms.step1.getFieldValue("application"), application: forms.step1.getFieldValue("application"),
organization: forms.step1.getFieldValue("organization"), organization: forms.step1.getFieldValue("organization"),
username: username username: username,
}).then((res) => { }).then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
const phone = res.data.phone; const phone = res.data.phone;
@@ -147,7 +147,7 @@ class ForgetPage extends React.Component {
name: this.state.name, name: this.state.name,
code: forms.step2.getFieldValue("emailCode"), code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix, phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login" type: "login",
}, oAuthParams).then(res => { }, oAuthParams).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]}); this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]});
@@ -468,7 +468,7 @@ class ForgetPage extends React.Component {
</Form.Item> </Form.Item>
<br /> <br />
<Form.Item hidden={this.state.current !== 2}> <Form.Item hidden={this.state.current !== 2}>
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}> <Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
{i18next.t("forget:Change Password")} {i18next.t("forget:Change Password")}
</Button> </Button>
</Form.Item> </Form.Item>

View File

@@ -20,35 +20,13 @@ import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Provider from "./Provider"; import * as Provider from "./Provider";
import * as ProviderButton from "./ProviderButton";
import * as Util from "./Util"; import * as Util from "./Util";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import SelfLoginButton from "./SelfLoginButton"; import SelfLoginButton from "./SelfLoginButton";
import {GithubLoginButton, GoogleLoginButton} from "react-social-login-buttons";
import FacebookLoginButton from "./FacebookLoginButton";
import QqLoginButton from "./QqLoginButton";
import DingTalkLoginButton from "./DingTalkLoginButton";
import GiteeLoginButton from "./GiteeLoginButton";
import WechatLoginButton from "./WechatLoginButton";
import WeiboLoginButton from "./WeiboLoginButton";
import i18next from "i18next"; import i18next from "i18next";
import LinkedInLoginButton from "./LinkedInLoginButton";
import WeComLoginButton from "./WeComLoginButton";
import LarkLoginButton from "./LarkLoginButton";
import GitLabLoginButton from "./GitLabLoginButton";
import AdfsLoginButton from "./AdfsLoginButton";
import BaiduLoginButton from "./BaiduLoginButton";
import AlipayLoginButton from "./AlipayLoginButton";
import CasdoorLoginButton from "./CasdoorLoginButton";
import InfoflowLoginButton from "./InfoflowLoginButton";
import AppleLoginButton from "./AppleLoginButton";
import AzureADLoginButton from "./AzureADLoginButton";
import SlackLoginButton from "./SlackLoginButton";
import SteamLoginButton from "./SteamLoginButton";
import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput"; import {CountDownInput} from "../common/CountDownInput";
import BilibiliLoginButton from "./BilibiliLoginButton";
const {TabPane} = Tabs; const {TabPane} = Tabs;
@@ -59,7 +37,7 @@ class LoginPage extends React.Component {
classes: props, classes: props,
type: props.type, type: props.type,
applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName), applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName),
owner : props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner), owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
application: null, application: null,
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin" mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
isCodeSignin: false, isCodeSignin: false,
@@ -68,7 +46,7 @@ class LoginPage extends React.Component {
validEmailOrPhone: false, validEmailOrPhone: false,
validEmail: false, validEmail: false,
validPhone: false, validPhone: false,
loginMethod: "password" loginMethod: "password",
}; };
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) { if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
@@ -146,6 +124,16 @@ class LoginPage extends React.Component {
} }
onFinish(values) { onFinish(values) {
if (this.state.loginMethod === "webAuthn") {
let username = this.state.username;
if (username === null || username === "") {
username = values["username"];
}
this.signInWithWebAuthn(username);
return;
}
const application = this.getApplicationObj(); const application = this.getApplicationObj();
const ths = this; const ths = this;
@@ -191,6 +179,10 @@ class LoginPage extends React.Component {
values["type"] = "saml"; values["type"] = "saml";
} }
if (this.state.owner != null) {
values["organization"] = this.state.owner;
}
AuthBackend.login(values, oAuthParams) AuthBackend.login(values, oAuthParams)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
@@ -203,6 +195,7 @@ class LoginPage extends React.Component {
} else if (responseType === "code") { } else if (responseType === "code") {
const code = res.data; const code = res.data;
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?"; const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
const noRedirect = oAuthParams.noRedirect;
if (Setting.hasPromptPage(application)) { if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("") AuthBackend.getAccount("")
@@ -224,7 +217,19 @@ class LoginPage extends React.Component {
} }
}); });
} else { } else {
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`); if (noRedirect === "true") {
window.close();
const newWindow = window.open(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
if (newWindow) {
setInterval(() => {
if (!newWindow.closed) {
newWindow.close();
}
}, 1000);
}
} else {
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
}
} }
// Util.showMessage("success", `Authorization code: ${res.data}`); // Util.showMessage("success", `Authorization code: ${res.data}`);
@@ -243,61 +248,6 @@ class LoginPage extends React.Component {
} }
} }
getSigninButton(type) {
const text = i18next.t("login:Sign in with {type}").replace("{type}", type);
if (type === "GitHub") {
return <GithubLoginButton text={text} align={"center"} />;
} else if (type === "Google") {
return <GoogleLoginButton text={text} align={"center"} />;
} else if (type === "QQ") {
return <QqLoginButton text={text} align={"center"} />;
} else if (type === "Facebook") {
return <FacebookLoginButton text={text} align={"center"} />;
} else if (type === "Weibo") {
return <WeiboLoginButton text={text} align={"center"} />;
} else if (type === "Gitee") {
return <GiteeLoginButton text={text} align={"center"} />;
} else if (type === "WeChat") {
return <WechatLoginButton text={text} align={"center"} />;
} else if (type === "DingTalk") {
return <DingTalkLoginButton text={text} align={"center"} />;
} else if (type === "LinkedIn") {
return <LinkedInLoginButton text={text} align={"center"} />;
} else if (type === "WeCom") {
return <WeComLoginButton text={text} align={"center"} />;
} else if (type === "Lark") {
return <LarkLoginButton text={text} align={"center"} />;
} else if (type === "GitLab") {
return <GitLabLoginButton text={text} align={"center"} />;
} else if (type === "Adfs") {
return <AdfsLoginButton text={text} align={"center"} />;
} else if (type === "Casdoor") {
return <CasdoorLoginButton text={text} align={"center"} />;
} else if (type === "Baidu") {
return <BaiduLoginButton text={text} align={"center"} />;
} else if (type === "Alipay") {
return <AlipayLoginButton text={text} align={"center"} />;
} else if (type === "Infoflow") {
return <InfoflowLoginButton text={text} align={"center"} />;
} else if (type === "Apple") {
return <AppleLoginButton text={text} align={"center"} />;
} else if (type === "AzureAD") {
return <AzureADLoginButton text={text} align={"center"} />;
} else if (type === "Slack") {
return <SlackLoginButton text={text} align={"center"} />;
} else if (type === "Steam") {
return <SteamLoginButton text={text} align={"center"} />;
} else if (type === "Bilibili") {
return <BilibiliLoginButton text={text} align={"center"} />;
} else if (type === "Okta") {
return <OktaLoginButton text={text} align={"center"} />;
} else if (type === "Douyin") {
return <DouyinLoginButton text={text} align={"center"} />;
}
return text;
}
getSamlUrl(provider) { getSamlUrl(provider) {
const params = new URLSearchParams(this.props.location.search); const params = new URLSearchParams(this.props.location.search);
let clientId = params.get("client_id"); let clientId = params.get("client_id");
@@ -315,35 +265,6 @@ class LoginPage extends React.Component {
}); });
} }
renderProviderLogo(provider, application, width, margin, size) {
if (size === "small") {
if (provider.category === "OAuth") {
return (
<a key={provider.displayName} href={Provider.getAuthUrl(application, provider, "signup")}>
<img width={width} height={width} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
} else if (provider.category === "SAML") {
return (
<a key={provider.displayName} onClick={this.getSamlUrl.bind(this, provider)}>
<img width={width} height={width} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
}
} else {
return (
<div key={provider.displayName} style={{marginBottom: "10px"}}>
<a href={Provider.getAuthUrl(application, provider, "signup")}>
{
this.getSigninButton(provider.type)
}
</a>
</div>
);
}
}
isProviderVisible(providerItem) { isProviderVisible(providerItem) {
if (this.state.mode === "signup") { if (this.state.mode === "signup") {
return Setting.isProviderVisibleForSignUp(providerItem); return Setting.isProviderVisibleForSignUp(providerItem);
@@ -370,7 +291,7 @@ class LoginPage extends React.Component {
<Button type="primary" key="signin"> <Button type="primary" key="signin">
Sign In Sign In
</Button> </Button>
</Link> </Link>,
]} ]}
> >
</Result> </Result>
@@ -418,7 +339,7 @@ class LoginPage extends React.Component {
rules={[ rules={[
{ {
required: true, required: true,
message: i18next.t("login:Please input your username, Email or phone!") message: i18next.t("login:Please input your username, Email or phone!"),
}, },
{ {
validator: (_, value) => { validator: (_, value) => {
@@ -438,8 +359,8 @@ class LoginPage extends React.Component {
this.setState({validEmailOrPhone: true}); this.setState({validEmailOrPhone: true});
return Promise.resolve(); return Promise.resolve();
} },
} },
]} ]}
> >
<Input <Input
@@ -482,19 +403,24 @@ class LoginPage extends React.Component {
</Button> </Button>
) : ) :
( (
<Button type="primary" style={{width: "100%", marginBottom: "5px"}} onClick={() => this.signInWithWebAuthn()}> <Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{i18next.t("login:Sign in with WebAuthn")} {i18next.t("login:Sign in with WebAuthn")}
</Button> </Button>
) )
} }
{ {
!application.enableSignUp ? null : this.renderFooter(application) this.renderFooter(application)
} }
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
{ {
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => { application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return this.renderProviderLogo(providerItem.provider, application, 30, 5, "small"); return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small");
}) })
} }
</Form.Item> </Form.Item>
@@ -515,19 +441,15 @@ class LoginPage extends React.Component {
<br /> <br />
{ {
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => { application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return this.renderProviderLogo(providerItem.provider, application, 40, 10, "big"); return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big");
}) })
} }
{ <div>
!application.enableSignUp ? null : ( <br />
<div> {
<br /> this.renderFooter(application)
{ }
this.renderFooter(application) </div>
}
</div>
)
}
</div> </div>
); );
} }
@@ -562,13 +484,19 @@ class LoginPage extends React.Component {
} }
</span> </span>
<span style={{float: "right"}}> <span style={{float: "right"}}>
{i18next.t("login:No account?")}&nbsp; {
<a onClick={() => { !application.enableSignUp ? null : (
sessionStorage.setItem("signinUrl", window.location.href); <>
Setting.goToSignup(this, application); {i18next.t("login:No account?")}&nbsp;
}}> <a onClick={() => {
{i18next.t("login:sign up now")} sessionStorage.setItem("signinUrl", window.location.href);
</a> Setting.goToSignup(this, application);
}}>
{i18next.t("login:sign up now")}
</a>
</>
)
}
</span> </span>
</React.Fragment> </React.Fragment>
); );
@@ -620,16 +548,16 @@ class LoginPage extends React.Component {
); );
} }
signInWithWebAuthn() { signInWithWebAuthn(username) {
if (this.state.username === null || this.state.username === "") { if (username === null || username === "") {
Setting.showMessage("error", "username is required for webauthn login"); Setting.showMessage("error", "username is required for webauthn login");
return; return;
} }
let application = this.getApplicationObj(); let application = this.getApplicationObj();
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}&name=${this.state.username}`, { return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}&name=${username}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}) })
.then(res => res.json()) .then(res => res.json())
.then((credentialRequestOptions) => { .then((credentialRequestOptions) => {
@@ -644,7 +572,7 @@ class LoginPage extends React.Component {
}); });
return navigator.credentials.get({ return navigator.credentials.get({
publicKey: credentialRequestOptions.publicKey publicKey: credentialRequestOptions.publicKey,
}); });
}) })
.then((assertion) => { .then((assertion) => {
@@ -666,7 +594,7 @@ class LoginPage extends React.Component {
signature: UserWebauthnBackend.webAuthnBufferEncode(sig), signature: UserWebauthnBackend.webAuthnBufferEncode(sig),
userHandle: UserWebauthnBackend.webAuthnBufferEncode(userHandle), userHandle: UserWebauthnBackend.webAuthnBufferEncode(userHandle),
}, },
}) }),
}) })
.then(res => res.json()).then((res) => { .then(res => res.json()).then((res) => {
if (res.msg === "") { if (res.msg === "") {
@@ -692,7 +620,7 @@ class LoginPage extends React.Component {
> >
<CountDownInput <CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone} disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
onButtonClickArgs={[this.state.username, "", Setting.getApplicationOrgName(application), true]} onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
/> />
</Form.Item> </Form.Item>
) : ( ) : (

View File

@@ -117,7 +117,7 @@ class PromptPage extends React.Component {
<div> <div>
{ {
(application === null || this.state.user === null) ? null : ( (application === null || this.state.user === null) ? null : (
application?.providers.filter(providerItem => Setting.isProviderPrompted(providerItem)).map((providerItem, index) => <OAuthWidget key={providerItem.name} labelSpan={6} user={this.state.user} application={application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />) application?.providers.filter(providerItem => Setting.isProviderPrompted(providerItem)).map((providerItem, index) => <OAuthWidget key={providerItem.name} labelSpan={6} user={this.state.user} application={application} providerItem={providerItem} account={this.props.account} onUnlinked={() => {return this.unlinked();}} />)
) )
} }
</div> </div>
@@ -202,7 +202,7 @@ class PromptPage extends React.Component {
Setting.goToLogin(this, application); Setting.goToLogin(this, application);
}}> }}>
Sign In Sign In
</Button> </Button>,
]} ]}
> >
</Result> </Result>

Some files were not shown because too many files have changed in this diff Show More