Compare commits

...

22 Commits

Author SHA1 Message Date
1d8b0a264e feat(login): add code login limit (#1442) 2023-01-06 18:51:43 +08:00
b525210835 feat: destroy session after delete user (#1441)
* fix: destroy session after delete user

* feat: visual session

* fix: go lint

* feat: add translation

* feat: auto flush after offline

* fix: delete one session

* fix: move 403 page to baseListPage
2023-01-06 15:04:13 +08:00
4ab2ca7a25 feat: fix checkPermissionForUpdateUser() logic (#1454)
* fix: fix `checkPermissionForUpdateUser()` logic

* fix: fix `checkPermissionForUpdateUser()` logic
2023-01-06 00:03:40 +08:00
dcf148fb7f fix: add GetMaskedRoles and GetMaskedPermissions when GetAccount (#1456) 2023-01-06 00:02:52 +08:00
c8846f1a2d feat: fix translate bug in UpdateUser() (#1451)
* fix: fix translate error

* fix translate bug in UpdateUser()

* Delete DiscordLoginButton.js
2023-01-04 22:54:50 +08:00
0559298d6c feat: extend user with roles and permissions in GetAccount (#1449) 2023-01-04 20:23:57 +08:00
ddb5e26fcd fix: mask user in get-account response (#1450) 2023-01-04 18:40:36 +08:00
Liu
1f39027b78 fix: convert line endings to LF on checkout for all envs (#1448)
* Convert line endings to LF on checkout for all envs

* fix: convert line endings to LF on checkout for all envs
2023-01-04 18:36:38 +08:00
eae3b0d367 feat: fix saml login failed by using oauth (#1443) 2023-01-03 19:42:12 +08:00
186f0ac97b feat: check permission when update user (#1438)
* feat: check permission when update user

* feat: check permission when update user

* fix: fix organization accountItem modifyRule

* fix: fix organization accountItem modifyRule
2023-01-02 09:27:25 +08:00
308f305c53 feat: add query and fragment response mode declare in OIDC (#1439) 2023-01-01 21:46:12 +08:00
d498bc60ce feat: edit user properties (#1435) 2022-12-31 15:27:53 +08:00
7bbe1e38c1 fix: fix translate error (#1432)
* fix:fix translate error

* Delete TelegramLoginButton.js

* Update data.json

* Update data.json

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-30 12:10:18 +08:00
f465fc6ce0 feat: support changing theme in antd 5 (#1430)
* feat: add global theme change function

* feat: add icons

* feat: in app theme changer

* feat: use antd built-in themes

* fix: multiple styling problem

* fix: theme init from localstorage

* feat: dark mode footer

* feat: casdoor logo color theme

* feat: select theme box icon adaptive to theme

* fix: menu bar style

* fix: language box style

* feat: translation

* feat: update translation of select theme box without reloading

* fix: mobile view

* fix: better structured select theme box

* feat: add compact icon

* fix: redundant theme fetch

* fix: redundant theme fetch

* fix: various styling problems
2022-12-29 22:30:37 +08:00
c952c2f2f4 feat: fix login with password bug when feature is disabled (#1428) 2022-12-27 14:46:57 +08:00
86ae97d1e5 feat: fix the bug that spin is always showing when response error (#1424) 2022-12-24 17:55:36 +08:00
6ea73e3eca fix: show background image in preview (#1425) 2022-12-24 17:47:05 +08:00
a71a190db5 feat: fix bug in redirectToLoginPage() (#1422) 2022-12-24 01:10:02 +08:00
da69d94445 feat: fix the bug that spin in oauth is always showing (#1421) 2022-12-23 15:06:51 +08:00
b8b915abe1 feat: check AccessPermission in multiple permissions (#1420) 2022-12-23 14:06:02 +08:00
5d1548e989 feat: fix absolute URL redirection (#1419)
* fix: redirect to absolute url

* fix: original jump
2022-12-23 11:05:15 +08:00
a0dc6e06cd feat: add EntryPage for login, signup pages to fix background flashing issue (#1416)
* feat: fix flush in login Pages

* fix: code format

* fix: improve code

* Update App.js

* Update EntryPage.js

* fix: optimize api request

* Update App.js

* Update App.js

* fix: fix css

* fix: css and getApllicationObj

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-22 23:39:02 +08:00
75 changed files with 13300 additions and 12015 deletions

3
.gitattributes vendored
View File

@ -1,2 +1,5 @@
*.go linguist-detectable=true *.go linguist-detectable=true
*.js linguist-detectable=false *.js linguist-detectable=false
# Declare files that will always have LF line endings on checkout.
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
*.sh text eol=lf

View File

@ -242,6 +242,7 @@ func (c *ApiController) Signup() {
// @router /logout [get,post] // @router /logout [get,post]
func (c *ApiController) Logout() { func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication() application := c.GetSessionApplication()
@ -271,12 +272,17 @@ func (c *ApiController) GetAccount() {
user = object.ExtendManagedAccountsWithUser(user) user = object.ExtendManagedAccountsWithUser(user)
} }
object.ExtendUserWithRolesAndPermissions(user)
user.Permissions = object.GetMaskedPermissions(user.Permissions)
user.Roles = object.GetMaskedRoles(user.Roles)
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user)) organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
resp := Response{ resp := Response{
Status: "ok", Status: "ok",
Sub: user.Id, Sub: user.Id,
Name: user.Name, Name: user.Name,
Data: user, Data: object.GetMaskedUser(user),
Data2: organization, Data2: organization,
} }
c.Data["json"] = resp c.Data["json"] = resp

View File

@ -103,12 +103,12 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token) resp = tokenToResponse(token)
} }
} else if form.Type == ResponseTypeSaml { // saml flow } else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host) res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
} }
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl} resp = &Response{Status: "ok", Msg: "", Data: res, Data2: map[string]string{"redirectUrl": redirectUrl, "method": method}}
} else if form.Type == ResponseTypeCas { } else if form.Type == ResponseTypeCas {
// not oauth but CAS SSO protocol // not oauth but CAS SSO protocol
service := c.Input().Get("service") service := c.Input().Get("service")
@ -139,6 +139,10 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
}) })
} }
if resp.Status == "ok" {
object.SetSession(user.GetId(), c.Ctx.Input.CruSession.SessionID())
}
return resp return resp
} }
@ -222,12 +226,13 @@ func (c *ApiController) Login() {
} }
// check result through Email or Phone // check result through Email or Phone
var checkDest string
if strings.Contains(form.Username, "@") { if strings.Contains(form.Username, "@") {
verificationCodeType = "email" verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username { if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email form.Username = user.Email
} }
checkResult = object.CheckVerificationCode(form.Username, form.Code, c.GetAcceptLanguage()) checkDest = form.Username
} else { } else {
verificationCodeType = "phone" verificationCodeType = "phone"
if len(form.PhonePrefix) == 0 { if len(form.PhonePrefix) == 0 {
@ -238,11 +243,16 @@ func (c *ApiController) Login() {
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username { if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone form.Username = user.Phone
} }
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username) checkDest = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code, c.GetAcceptLanguage())
} }
user = object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The user: %s/%s doesn't exist"), form.Organization, form.Username))
return
}
checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 { if len(checkResult) != 0 {
responseText := fmt.Sprintf("%s%s", verificationCodeType, checkResult) responseText := fmt.Sprintf("%s - %s", verificationCodeType, checkResult)
c.ResponseError(responseText) c.ResponseError(responseText)
return return
} }
@ -253,18 +263,16 @@ func (c *ApiController) Login() {
} else { } else {
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)) object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
} }
user = object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The user: %s/%s doesn't exist"), form.Organization, form.Username))
return
}
} else { } else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil { if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application)) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
return return
} }
if !application.EnablePassword {
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
return
}
if object.CheckToEnableCaptcha(application) { if object.CheckToEnableCaptcha(application) {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret) isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)

68
controllers/session.go Normal file
View File

@ -0,0 +1,68 @@
// 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 controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// DeleteSession
// @Title DeleteSession
// @Tag Session API
// @Description Delete session by userId
// @Param ID query string true "The ID(owner/name) of user."
// @Success 200 {array} string The Response object
// @router /delete-session [post]
func (c *ApiController) DeleteSession() {
var session object.Session
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteSession(util.GetId(session.Owner, session.Name)))
c.ServeJSON()
}
// GetSessions
// @Title GetSessions
// @Tag Session API
// @Description Get organization user sessions
// @Param owner query string true "The organization name"
// @Success 200 {array} string The Response object
// @router /get-sessions [get]
func (c *ApiController) GetSessions() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
owner := c.Input().Get("owner")
if limit == "" || page == "" {
c.Data["json"] = object.GetSessions(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSessionCount(owner, field, value)))
sessions := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(sessions, paginator.Nums())
}
}

View File

@ -125,6 +125,127 @@ func (c *ApiController) GetUser() {
c.ServeJSON() c.ServeJSON()
} }
func checkPermissionForUpdateUser(id string, newUser object.User, c *ApiController) (bool, string) {
oldUser := object.GetUser(id)
org := object.GetOrganizationByUser(oldUser)
var itemsChanged []*object.AccountItem
if oldUser.Owner != newUser.Owner {
item := object.GetAccountItemByName("Organization", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Name != newUser.Name {
item := object.GetAccountItemByName("Name", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Id != newUser.Id {
item := object.GetAccountItemByName("ID", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.DisplayName != newUser.DisplayName {
item := object.GetAccountItemByName("Display name", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Avatar != newUser.Avatar {
item := object.GetAccountItemByName("Avatar", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Type != newUser.Type {
item := object.GetAccountItemByName("User type", org)
itemsChanged = append(itemsChanged, item)
}
// The password is *** when not modified
if oldUser.Password != newUser.Password && newUser.Password != "***" {
item := object.GetAccountItemByName("Password", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Email != newUser.Email {
item := object.GetAccountItemByName("Email", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Phone != newUser.Phone {
item := object.GetAccountItemByName("Phone", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Region != newUser.Region {
item := object.GetAccountItemByName("Country/Region", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Location != newUser.Location {
item := object.GetAccountItemByName("Location", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Affiliation != newUser.Affiliation {
item := object.GetAccountItemByName("Affiliation", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Title != newUser.Title {
item := object.GetAccountItemByName("Title", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Homepage != newUser.Homepage {
item := object.GetAccountItemByName("Homepage", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Bio != newUser.Bio {
item := object.GetAccountItemByName("Bio", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Tag != newUser.Tag {
item := object.GetAccountItemByName("Tag", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.SignupApplication != newUser.SignupApplication {
item := object.GetAccountItemByName("Signup application", org)
itemsChanged = append(itemsChanged, item)
}
oldUserRolesJson, _ := json.Marshal(oldUser.Roles)
newUserRolesJson, _ := json.Marshal(newUser.Roles)
if string(oldUserRolesJson) != string(newUserRolesJson) {
item := object.GetAccountItemByName("Roles", org)
itemsChanged = append(itemsChanged, item)
}
oldUserPermissionJson, _ := json.Marshal(oldUser.Permissions)
newUserPermissionJson, _ := json.Marshal(newUser.Permissions)
if string(oldUserPermissionJson) != string(newUserPermissionJson) {
item := object.GetAccountItemByName("Permissions", org)
itemsChanged = append(itemsChanged, item)
}
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
item := object.GetAccountItemByName("Properties", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsAdmin != newUser.IsAdmin {
item := object.GetAccountItemByName("Is admin", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
item := object.GetAccountItemByName("Is global admin", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsForbidden != newUser.IsForbidden {
item := object.GetAccountItemByName("Is forbidden", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsDeleted != newUser.IsDeleted {
item := object.GetAccountItemByName("Is deleted", org)
itemsChanged = append(itemsChanged, item)
}
for i := range itemsChanged {
if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], c.getCurrentUser(), c.GetAcceptLanguage()); !pass {
return pass, err
}
}
return true, ""
}
// UpdateUser // UpdateUser
// @Title UpdateUser // @Title UpdateUser
// @Tag User API // @Tag User API
@ -159,6 +280,12 @@ func (c *ApiController) UpdateUser() {
} }
isGlobalAdmin := c.IsGlobalAdmin() isGlobalAdmin := c.IsGlobalAdmin()
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
c.ResponseError(err)
return
}
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin) affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
if affected { if affected {
object.UpdateUserToOriginalDatabase(&user) object.UpdateUserToOriginalDatabase(&user)

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,6 +25,7 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "Username must have at least 2 characters",
"You don't have the permission to do this": "You don't have the permission to do this", "You don't have the permission to do this": "You don't have the permission to do this",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "You have entered the wrong password too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances" "password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "Please sign in first" "Please sign in first": "Please sign in first"

View File

@ -25,13 +25,14 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定: %s (%s)",
"The application: %s does not exist": "应用 %s 不存在", "The application: %s does not exist": "应用 %s 不存在",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "不支持该类型的提供商: %s", "The provider type: %s is not supported": "不支持该类型的提供商: %s",
"The provider: %s is not enabled for the application": "提供商: %s 未被启用", "The provider: %s is not enabled for the application": "提供商: %s 未被启用",
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员", "The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员",
"The user: %s/%s doesn't exist": "用户不存在: %s/%s", "The user: %s/%s doesn't exist": "用户不存在: %s/%s",
"Turing test failed.": "真人验证失败", "Turing test failed.": "真人验证失败",
"Unauthorized operation": "未授权的操作", "Unauthorized operation": "未授权的操作",
"Unknown authentication type (not password or provider), form = %s": "未授权的操作" "Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "服务 %s 与 %s 不匹配" "Service %s and %s do not match": "服务 %s 与 %s 不匹配"
@ -66,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "用户名过长最大长度为39个字符", "Username is too long (maximum is 39 characters).": "用户名过长最大长度为39个字符",
"Username must have at least 2 characters": "用户名至少要有2个字符", "Username must have at least 2 characters": "用户名至少要有2个字符",
"You don't have the permission to do this": "用户名至少要有2个字符", "You don't have the permission to do this": "用户名至少要有2个字符",
"You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again": "输入密码错误次数已达上限,请在 %d 分 %d 秒后重试", "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again": "You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again",
"unsupported password type: %s": "不支持的密码类型: %s" "unsupported password type: %s": "不支持的密码类型: %s"
}, },
"check_util": { "check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "输入密码错误次数已达上限,请在 %d 分后重试", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"password is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会" "password or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会"
}, },
"enforcer": { "enforcer": {
"Please sign in first": "请先登录" "Please sign in first": "请先登录"
@ -89,7 +90,7 @@
"You can't unlink yourself, you are not a member of any application": "您无法取消链接,您不是任何应用程序的成员" "You can't unlink yourself, you are not a member of any application": "您无法取消链接,您不是任何应用程序的成员"
}, },
"organization": { "organization": {
"Only admin can modify the %s.": "您无法取消链接,您不是任何应用程序的成员", "Only admin can modify the %s.": "仅允许管理员可以修改 %s",
"The %s is immutable.": "%s是不可变的", "The %s is immutable.": "%s是不可变的",
"Unknown modify rule %s.": "未知的修改规则" "Unknown modify rule %s.": "未知的修改规则"
}, },

View File

@ -222,6 +222,11 @@ func (a *Adapter) createTable() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(Session))
if err != nil {
panic(err)
}
} }
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session { func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {

View File

@ -147,7 +147,7 @@ func checkSigninErrorTimes(user *User, lang string) string {
// deny the login if the error times is greater than the limit and the last login time is less than the duration // deny the login if the error times is greater than the limit and the last login time is less than the duration
if seconds > 0 { if seconds > 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again"), seconds/60, seconds%60) return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes %d seconds and try again"), seconds/60, seconds%60)
} }
// reset the error times // reset the error times
@ -313,8 +313,9 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return true, err return true, err
} }
enforcer := getEnforcer(permission) enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read") if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
break return allowed, err
}
} }
} }
return allowed, err return allowed, err

View File

@ -58,9 +58,9 @@ func recordSigninErrorInfo(user *User, lang string) string {
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin) UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances > 0 { if leftChances > 0 {
return fmt.Sprintf(i18n.Translate(lang, "check_util:password is incorrect, you have %d remaining chances"), leftChances) return fmt.Sprintf(i18n.Translate(lang, "check_util:password or code is incorrect, you have %d remaining chances"), leftChances)
} }
// don't show the chance error message if the user has no chance left // don't show the chance error message if the user has no chance left
return fmt.Sprintf(i18n.Translate(lang, "check_util:You have entered the wrong password too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes())) return fmt.Sprintf(i18n.Translate(lang, "check_util:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
} }

View File

@ -76,7 +76,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend), JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend), IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"}, ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
ResponseModesSupported: []string{"login", "code", "link"}, ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"}, GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
IdTokenSigningAlgValuesSupported: []string{"RS256"}, IdTokenSigningAlgValuesSupported: []string{"RS256"},

View File

@ -269,3 +269,12 @@ func ContainsAsterisk(userId string, users []string) bool {
return containsAsterisk return containsAsterisk
} }
func GetMaskedPermissions(permissions []*Permission) []*Permission {
for _, permission := range permissions {
permission.Users = nil
permission.Submitter = ""
}
return permissions
}

View File

@ -192,3 +192,11 @@ func roleChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
func GetMaskedRoles(roles []*Role) []*Role {
for _, role := range roles {
role.Users = nil
}
return roles
}

View File

@ -223,11 +223,14 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
// GetSamlResponse generates a SAML2.0 response // GetSamlResponse generates a SAML2.0 response
// parameter samlRequest is saml request in base64 format // parameter samlRequest is saml request in base64 format
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) { func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
// request type
method := "GET"
// base64 decode // base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest) defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
// decompress // decompress
var buffer bytes.Buffer var buffer bytes.Buffer
@ -236,12 +239,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
var authnRequest saml.AuthnRequest var authnRequest saml.AuthnRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest) err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
// verify samlRequest // verify samlRequest
if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid { if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
return "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url) return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
} }
// get certificate string // get certificate string
@ -253,6 +256,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
// redirect Url (Assertion Consumer Url) // redirect Url (Assertion Consumer Url)
if application.SamlReplyUrl != "" { if application.SamlReplyUrl != "" {
method = "POST"
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
} }
@ -275,7 +279,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
doc.SetRoot(samlResponse) doc.SetRoot(samlResponse)
xmlBytes, err := doc.WriteToBytes() xmlBytes, err := doc.WriteToBytes()
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
// compress // compress
@ -283,7 +287,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
flated := bytes.NewBuffer(nil) flated := bytes.NewBuffer(nil)
writer, err := flate.NewWriter(flated, flate.DefaultCompression) writer, err := flate.NewWriter(flated, flate.DefaultCompression)
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
writer.Write(xmlBytes) writer.Write(xmlBytes)
writer.Close() writer.Close()
@ -291,7 +295,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} }
// base64 encode // base64 encode
res := base64.StdEncoding.EncodeToString(xmlBytes) res := base64.StdEncoding.EncodeToString(xmlBytes)
return res, authnRequest.AssertionConsumerServiceURL, nil return res, authnRequest.AssertionConsumerServiceURL, method, nil
} }
// NewSamlResponse11 return a saml1.1 response(not 2.0) // NewSamlResponse11 return a saml1.1 response(not 2.0)

132
object/session.go Normal file
View File

@ -0,0 +1,132 @@
// 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 object
import (
"time"
"github.com/beego/beego"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Session struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
SessionId []string `json:"sessionId"`
}
func SetSession(id string, sessionId string) {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
session := &Session{Owner: owner, Name: name}
get, err := adapter.Engine.Get(session)
if err != nil {
panic(err)
}
session.SessionId = append(session.SessionId, sessionId)
if get {
_, err = adapter.Engine.ID(core.PK{owner, name}).Update(session)
} else {
session.CreatedTime = time.Now().Format(time.RFC3339)
_, err = adapter.Engine.Insert(session)
}
if err != nil {
panic(err)
}
}
func DeleteSession(id string) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
session := &Session{Owner: owner, Name: name}
_, err := adapter.Engine.ID(core.PK{owner, name}).Get(session)
if err != nil {
return false
}
DeleteBeegoSession(session.SessionId)
affected, err := adapter.Engine.ID(core.PK{owner, name}).Delete(session)
return affected != 0
}
func DeleteSessionId(id string, sessionId string) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
session := &Session{Owner: owner, Name: name}
_, err := adapter.Engine.ID(core.PK{owner, name}).Get(session)
if err != nil {
return false
}
DeleteBeegoSession([]string{sessionId})
session.SessionId = util.DeleteVal(session.SessionId, sessionId)
if len(session.SessionId) < 1 {
affected, _ := adapter.Engine.ID(core.PK{owner, name}).Delete(session)
return affected != 0
} else {
affected, _ := adapter.Engine.ID(core.PK{owner, name}).Update(session)
return affected != 0
}
}
func DeleteBeegoSession(sessionIds []string) {
for _, sessionId := range sessionIds {
err := beego.GlobalSessions.GetProvider().SessionDestroy(sessionId)
if err != nil {
return
}
}
}
func GetSessions(owner string) []*Session {
sessions := []*Session{}
var err error
if owner != "" {
err = adapter.Engine.Desc("created_time").Where("owner = ?", owner).Find(&sessions)
} else {
err = adapter.Engine.Desc("created_time").Find(&sessions)
}
if err != nil {
panic(err)
}
return sessions
}
func GetPaginationSessions(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Session {
sessions := []*Session{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&sessions)
if err != nil {
panic(err)
}
return sessions
}
func GetSessionCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Session{})
if err != nil {
panic(err)
}
return int(count)
}

View File

@ -529,6 +529,9 @@ func AddUsersInBatch(users []*User) bool {
} }
func DeleteUser(user *User) bool { func DeleteUser(user *User) bool {
// Forced offline the user first
DeleteSession(user.GetId())
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{}) affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{})
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -26,6 +26,10 @@ import (
"xorm.io/core" "xorm.io/core"
) )
const (
wrongCode = "wrongCode"
)
type VerificationRecord struct { type VerificationRecord struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -167,7 +171,7 @@ func CheckVerificationCode(dest, code, lang string) string {
} }
if record.Code != code { if record.Code != code {
return "Wrong code!" return wrongCode
} }
return "" return ""
@ -186,6 +190,24 @@ func DisableVerificationCode(dest string) {
} }
} }
func CheckSigninCode(user *User, dest, code, lang string) string {
// check the login error times
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
}
result := CheckVerificationCode(dest, code, lang)
switch result {
case "":
resetUserSigninErrorTimes(user)
return ""
case wrongCode:
return recordSigninErrorInfo(user, lang)
default:
return result
}
}
// From Casnode/object/validateCode.go line 116 // From Casnode/object/validateCode.go line 116
var stdNums = []byte("0123456789") var stdNums = []byte("0123456789")

View File

@ -164,6 +164,9 @@ func initAPI() {
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter") beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord") beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
beego.Router("/api/delete-session", &controllers.ApiController{}, "POST:DeleteSession")
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks") beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook") beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook") beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")

25
util/slice.go Normal file
View File

@ -0,0 +1,25 @@
// 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 util
func DeleteVal(values []string, val string) []string {
newValues := []string{}
for _, v := range values {
if v != val {
newValues = append(newValues, v)
}
}
return newValues
}

View File

@ -9,7 +9,7 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"antd": "5.0.5", "antd": "5.1.2",
"codemirror": "^5.61.1", "codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0", "core-js": "^3.25.0",

View File

@ -170,7 +170,7 @@ class AccountTable extends React.Component {
} }
let options; let options;
if (record.viewRule === "Admin") { if (record.viewRule === "Admin" || record.name === "Is admin" || record.name === "Is global admin") {
options = [ options = [
{id: "Admin", name: "Admin"}, {id: "Admin", name: "Admin"},
{id: "Immutable", name: "Immutable"}, {id: "Immutable", name: "Immutable"},

View File

@ -260,6 +260,13 @@ class AdapterListPage 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

@ -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 {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd"; import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, theme} 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";
@ -55,26 +55,22 @@ import CustomGithubCorner from "./CustomGithubCorner";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import * as Auth from "./auth/Auth"; import * as Auth from "./auth/Auth";
import SignupPage from "./auth/SignupPage"; import EntryPage from "./EntryPage";
import ResultPage from "./auth/ResultPage"; import ResultPage from "./auth/ResultPage";
import LoginPage from "./auth/LoginPage";
import SelfLoginPage from "./auth/SelfLoginPage";
import SelfForgetPage from "./auth/SelfForgetPage";
import ForgetPage from "./auth/ForgetPage";
import * as AuthBackend from "./auth/AuthBackend"; import * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback"; import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from "./SelectLanguageBox"; import SelectLanguageBox from "./SelectLanguageBox";
import i18next from "i18next"; import i18next from "i18next";
import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from "./auth/SamlCallback"; import SamlCallback from "./auth/SamlCallback";
import CasLogout from "./auth/CasLogout";
import ModelListPage from "./ModelListPage"; import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage"; import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo"; import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage"; import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage"; import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next"; import {withTranslation} from "react-i18next";
import SelectThemeBox from "./SelectThemeBox";
import SessionListPage from "./SessionListPage";
const {Header, Footer, Content} = Layout; const {Header, Footer, Content} = Layout;
@ -87,6 +83,8 @@ class App extends Component {
account: undefined, account: undefined,
uri: null, uri: null,
menuVisible: false, menuVisible: false,
themeAlgorithm: null,
logo: null,
}; };
Setting.initServerUrl(); Setting.initServerUrl();
@ -101,6 +99,16 @@ class App extends Component {
this.getAccount(); this.getAccount();
} }
componentDidMount() {
localStorage.getItem("theme") ?
this.setState({"themeAlgorithm": this.getTheme()}) : this.setState({"themeAlgorithm": theme.defaultAlgorithm});
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
addEventListener("themeChange", (e) => {
this.setState({"themeAlgorithm": this.getTheme()});
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
});
}
componentDidUpdate() { componentDidUpdate() {
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
const uri = location.pathname; const uri = location.pathname;
@ -190,6 +198,10 @@ class App extends Component {
return ""; return "";
} }
getTheme() {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"];
}
setLanguage(account) { setLanguage(account) {
const language = account?.language; const language = account?.language;
if (language !== "" && language !== i18next.language) { if (language !== "" && language !== i18next.language) {
@ -410,6 +422,10 @@ class App extends Component {
"/tokens" "/tokens"
)); ));
res.push(Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>,
"/sessions"
));
res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>,
"/webhooks" "/webhooks"
)); ));
@ -471,54 +487,63 @@ class App extends Component {
renderRouter() { renderRouter() {
return ( return (
<div> <ConfigProvider theme={{
<Switch> token: {
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} /> colorPrimary: "rgb(89,54,213)",
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} /> colorInfo: "rgb(89,54,213)",
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} /> },
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} /> algorithm: this.state.themeAlgorithm,
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} /> }}>
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} /> <div>
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} /> <Switch>
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} /> <Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} /> <Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} /> <Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} /> <Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} /> <Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} /> <Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} /> <Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} /> <Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} /> <Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} /> <Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/} <Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} /> <Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} /> <Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} /> <Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} /> <Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} /> <Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} /> <Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} /> {/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} /> <Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} /> <Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} /> <Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} /> <Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} /> <Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} /> <Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")} <Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} /> <Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
</Switch> <Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
</div> <Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
</div>
</ConfigProvider>
); );
} }
@ -538,30 +563,27 @@ class App extends Component {
if (!Setting.isMobile()) { if (!Setting.isMobile()) {
return ( return (
<Layout id="parent-area"> <Layout id="parent-area">
<Header style={{marginBottom: "3px", paddingInline: 0}}> <Header style={{marginBottom: "3px", paddingInline: 0, backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
) )
} }
<div> <Menu
<Menu items={this.renderMenu()}
// theme="dark" mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
items={this.renderMenu()} selectedKeys={[`${this.state.selectedMenuKey}`]}
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}
selectedKeys={[`${this.state.selectedMenuKey}`]} />
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}} {
> this.renderAccount()
</Menu> }
{ {this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
this.renderAccount() {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header> </Header>
<Content style={{backgroundColor: "#f5f5f5", alignItems: "stretch", display: "flex", flexDirection: "column"}}> <Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
<Card className="content-warp-card"> <Card className="content-warp-card">
{ {
this.renderRouter() this.renderRouter()
@ -576,11 +598,11 @@ class App extends Component {
} else { } else {
return ( return (
<Layout> <Layout>
<Header style={{padding: "0", marginBottom: "3px"}}> <Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
) )
} }
@ -598,12 +620,11 @@ class App extends Component {
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text"> <Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")} {i18next.t("general:Menu")}
</Button> </Button>
<div style = {{float: "right"}}> {
{ this.renderAccount()
this.renderAccount() }
} {this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />} {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header> </Header>
<Content style={{display: "flex", flexDirection: "column"}} >{ <Content style={{display: "flex", flexDirection: "column"}} >{
this.renderRouter()} this.renderRouter()}
@ -619,74 +640,66 @@ class App extends Component {
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ // https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return ( return (
<> <React.Fragment>
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />} {!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
<Footer id="footer" style={ <Footer id="footer" style={
{ {
borderTop: "1px solid #e8e8e8",
backgroundColor: "white",
textAlign: "center", textAlign: "center",
} }
}> }>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a> Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
</Footer> </Footer>
</> </React.Fragment>
); );
} }
isDoorPages() { isDoorPages() {
return this.isEntryPages() || window.location.pathname.startsWith("/callback");
}
isEntryPages() {
return window.location.pathname.startsWith("/signup") || return window.location.pathname.startsWith("/signup") ||
window.location.pathname.startsWith("/login") || window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/callback") ||
window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/forget") || window.location.pathname.startsWith("/forget") ||
window.location.pathname.startsWith("/cas"); window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/cas") ||
window.location.pathname.startsWith("/auto-signup");
} }
renderPage() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
return ( return (
<> <React.Fragment>
<Layout id="parent-area"> <Layout id="parent-area">
<Content style={{display: "flex", justifyContent: "center"}}> <Content style={{display: "flex", justifyContent: "center"}}>
<Switch> {
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} /> this.isEntryPages() ?
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} /> <EntryPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} /> :
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} /> <Switch>
<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="/callback" component={AuthCallback} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
<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);}} />} /> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} /> </Switch>
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} /> }
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
</Content> </Content>
{ {
this.renderFooter() this.renderFooter()
} }
</Layout> </Layout>
</> </React.Fragment>
); );
} }
return ( return (
<> <React.Fragment>
<FloatButton.BackTop /> <FloatButton.BackTop />
<CustomGithubCorner /> <CustomGithubCorner />
{ {
this.renderContent() this.renderContent()
} }
</> </React.Fragment>
); );
} }
@ -702,6 +715,7 @@ class App extends Component {
colorPrimary: "rgb(89,54,213)", colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)",
}, },
algorithm: this.state.themeAlgorithm,
}}> }}>
{ {
this.renderPage() this.renderPage()
@ -723,6 +737,7 @@ class App extends Component {
colorPrimary: "rgb(89,54,213)", colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)",
}, },
algorithm: this.state.themeAlgorithm,
}}> }}>
{ {
this.renderPage() this.renderPage()

View File

@ -13,14 +13,12 @@
} }
.App-header { .App-header {
background-color: #282c34;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: white;
} }
.App-link { .App-link {
@ -41,7 +39,6 @@ img {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5;
} }
.panel-logo { .panel-logo {
@ -55,7 +52,7 @@ img {
background-repeat: no-repeat; background-repeat: no-repeat;
border-radius: 5px; border-radius: 5px;
width: 45px; width: 45px;
height: 65px; height: 100%;
float: right; float: right;
cursor: pointer; cursor: pointer;
@ -64,9 +61,32 @@ img {
} }
} }
.login-form .language-box {
height: 65px;
}
.theme-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px;
background-position: center !important;
background-repeat: no-repeat !important;
border-radius: 5px;
width: 45px;
height: 100%;
float: right;
cursor: pointer;
&:hover {
background-color: #f5f5f5 !important;
}
}
.rightDropDown { .rightDropDown {
border-radius: 7px;
&:hover { &:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
color: black;
} }
} }
@ -128,3 +148,9 @@ img {
background-size: 100% 100%; background-size: 100% 100%;
background-attachment: fixed; background-attachment: fixed;
} }
.ant-menu-horizontal {
border-bottom: none !important;
margin-right: 30px;
right: 230px;
}

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Popover, Radio, Row, Select, Switch, Upload} from "antd"; import {Button, Card, Col, Input, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons"; import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as CertBackend from "./backend/CertBackend"; import * as CertBackend from "./backend/CertBackend";
@ -103,6 +103,7 @@ class ApplicationEditPage extends React.Component {
uploading: false, uploading: false,
mode: props.location.mode !== undefined ? props.location.mode : "edit", mode: props.location.mode !== undefined ? props.location.mode : "edit",
samlMetadata: null, samlMetadata: null,
isAuthorized: true,
}; };
} }
@ -129,9 +130,15 @@ class ApplicationEditPage extends React.Component {
getOrganizations() { getOrganizations() {
OrganizationBackend.getOrganizations("admin") OrganizationBackend.getOrganizations("admin")
.then((res) => { .then((res) => {
this.setState({ if (res?.status === "error") {
organizations: (res.msg === undefined) ? res : [], this.setState({
}); isAuthorized: false,
});
} else {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
}
}); });
} }
@ -726,7 +733,7 @@ class ApplicationEditPage extends React.Component {
renderSignupSigninPreview() { renderSignupSigninPreview() {
let signUpUrl = `/signup/${this.state.application.name}`; let signUpUrl = `/signup/${this.state.application.name}`;
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`; const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"}; const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
if (!this.state.application.enablePassword) { if (!this.state.application.enablePassword) {
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize"); signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
} }
@ -742,15 +749,19 @@ 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: previewWidth, 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: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
{ {
this.state.application.enablePassword ? ( this.state.application.enablePassword ? (
<SignupPage application={this.state.application} /> <div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
<SignupPage application={this.state.application} preview = "auto" />
</div>
) : ( ) : (
<LoginPage type={"login"} mode={"signup"} application={this.state.application} /> <div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
<LoginPage type={"login"} mode={"signup"} application={this.state.application} preview = "auto" />
</div>
) )
} }
<div style={maskStyle} /> <div style={{overflow: "auto", ...maskStyle}} />
</div> </div>
</Col> </Col>
<Col span={previewGrid}> <Col span={previewGrid}>
@ -762,9 +773,11 @@ 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: previewWidth, 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: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} /> <div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
<div style={maskStyle} /> <LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" />
</div>
<div style={{overflow: "auto", ...maskStyle}} />
</div> </div>
</Col> </Col>
</React.Fragment> </React.Fragment>
@ -832,6 +845,17 @@ class ApplicationEditPage extends React.Component {
} }
render() { render() {
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 or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
{ {

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, Col, List, Popconfirm, Row, Table, Tooltip} from "antd"; import {Button, Col, List, Popconfirm, Result, Row, Table, Tooltip} from "antd";
import {EditOutlined} from "@ant-design/icons"; import {EditOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -36,6 +36,7 @@ class ApplicationListPage extends BaseListPage {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }
@ -258,6 +259,17 @@ class ApplicationListPage 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 or logged in status invalid.")}
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={applications} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
@ -292,6 +304,13 @@ class ApplicationListPage 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

@ -13,9 +13,10 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Input, Space} from "antd"; import {Button, Input, Result, Space} from "antd";
import {SearchOutlined} from "@ant-design/icons"; import {SearchOutlined} from "@ant-design/icons";
import Highlighter from "react-highlight-words"; import Highlighter from "react-highlight-words";
import i18next from "i18next";
class BaseListPage extends React.Component { class BaseListPage extends React.Component {
constructor(props) { constructor(props) {
@ -127,6 +128,17 @@ class BaseListPage extends React.Component {
}; };
render() { render() {
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 or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
{ {

View File

@ -227,6 +227,13 @@ class CertListPage 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,
});
}
} }
}); });
}; };

84
web/src/EntryPage.js Normal file
View File

@ -0,0 +1,84 @@
// 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.
import React from "react";
import {Redirect, Route, Switch} from "react-router-dom";
import {Spin} from "antd";
import i18next from "i18next";
import * as Setting from "./Setting";
import SignupPage from "./auth/SignupPage";
import SelfLoginPage from "./auth/SelfLoginPage";
import LoginPage from "./auth/LoginPage";
import SelfForgetPage from "./auth/SelfForgetPage";
import ForgetPage from "./auth/ForgetPage";
import PromptPage from "./auth/PromptPage";
import CasLogout from "./auth/CasLogout";
class EntryPage extends React.Component {
constructor(props) {
super(props);
this.state = {
application: undefined,
};
}
renderHomeIfLoggedIn(component) {
if (this.props.account !== null && this.props.account !== undefined) {
return <Redirect to="/" />;
} else {
return component;
}
}
renderLoginIfNotLoggedIn(component) {
if (this.props.account === null) {
sessionStorage.setItem("from", window.location.pathname);
return <Redirect to="/login" />;
} else if (this.props.account === undefined) {
return null;
} else {
return component;
}
}
render() {
const onUpdateApplication = (application) => {
this.setState({
application: application,
});
};
return <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signup"} onUpdateApplication={onUpdateApplication}{...props} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} />
</Switch>
</div>;
}
}
export default EntryPage;

View File

@ -200,6 +200,13 @@ class ModelListPage 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

@ -251,7 +251,7 @@ class OrganizationListPage extends BaseListPage {
<Result <Result
status="403" status="403"
title="403 Unauthorized" title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page.")} subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/> />
); );

View File

@ -278,6 +278,13 @@ class PaymentListPage 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

@ -358,6 +358,13 @@ class PermissionListPage 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

@ -295,6 +295,13 @@ class ProductListPage 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

@ -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, Table} from "antd"; import {Button, Popconfirm, Result, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
@ -36,6 +36,7 @@ class ProviderListPage extends BaseListPage {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }
newProvider() { newProvider() {
@ -227,6 +228,17 @@ class ProviderListPage 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 or logged in status invalid.")}
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={providers} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps}
@ -268,6 +280,13 @@ class ProviderListPage 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

@ -222,6 +222,13 @@ class RecordListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.data.includes("Please login first")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Popconfirm, Table, Upload} from "antd"; import {Button, Popconfirm, Result, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -37,6 +37,7 @@ class ResourceListPage extends BaseListPage {
searchedColumn: "", searchedColumn: "",
fileList: [], fileList: [],
uploading: false, uploading: false,
isAuthorized: true,
}; };
} }
@ -272,6 +273,17 @@ class ResourceListPage 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 or logged in status invalid.")}
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={resources} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
@ -308,6 +320,13 @@ class ResourceListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.data.includes("Please login first")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -231,6 +231,13 @@ class RoleListPage 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,
});
}
} }
}); });
}; };

81
web/src/SelectThemeBox.js Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import * as Setting from "./Setting";
import {Dropdown} from "antd";
import "./App.less";
import i18next from "i18next";
function themeIcon(themeKey) {
return <img width={24} alt={themeKey} src={getLogoURL(themeKey)} />;
}
function getLogoURL(themeKey) {
if (themeKey) {
return Setting.Themes.find(t => t.key === themeKey)["selectThemeLogo"];
} else {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["selectThemeLogo"];
}
}
class SelectThemeBox extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
themes: props.theme ?? ["Default", "Dark", "Compact"],
icon: null,
};
}
items = this.getThemes();
componentDidMount() {
i18next.on("languageChanged", () => {
this.items = this.getThemes();
});
localStorage.getItem("theme") ? this.setState({"icon": getLogoURL()}) : this.setState({"icon": getLogoURL("Default")});
addEventListener("themeChange", (e) => {
this.setState({"icon": getLogoURL()});
});
}
getThemes() {
return Setting.Themes.map((theme) => Setting.getItem(i18next.t(`general:${theme.label}`), theme.key, themeIcon(theme.key)));
}
getOrganizationThemes(themes) {
const select = [];
for (const theme of themes) {
this.items.map((item, index) => item.key === theme ? select.push(item) : null);
}
return select;
}
render() {
const themeItems = this.getOrganizationThemes(this.state.themes);
const onClick = (e) => {
Setting.setTheme(e.key);
};
return (
<Dropdown menu={{items: themeItems, onClick}} >
<div className="theme-box" style={{display: themeItems.length === 0 ? "none" : null, background: `url(${this.state.icon})`, ...this.props.style}} />
</Dropdown>
);
}
}
export default SelectThemeBox;

162
web/src/SessionListPage.js Normal file
View File

@ -0,0 +1,162 @@
// 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.
import BaseListPage from "./BaseListPage";
import * as Setting from "./Setting";
import i18next from "i18next";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table, Tag} from "antd";
import React from "react";
import * as SessionBackend from "./backend/SessionBackend";
class SessionListPage extends BaseListPage {
deleteSession(i) {
SessionBackend.deleteSession(this.state.data[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
renderTable(sessions) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "150px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "organization",
width: "110px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
key: "createdTime",
width: "180px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Session ID"),
dataIndex: "sessionId",
key: "sessionId",
width: "180px",
sorter: true,
render: (text, record, index) => {
return text.map((item, index) =>
<Tag key={index}>{item}</Tag>
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "70px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Popconfirm
title={`Sure to delete session: ${record.name} ?`}
onConfirm={() => this.deleteSession(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);
},
},
];
const paginationProps = {
total: this.state.pagination.total,
showQuickJumper: true,
showSizeChanger: true,
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
};
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={sessions} rowKey="name" size="middle" bordered pagination={paginationProps}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
</div>
);
}
fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
if (params.contentType !== undefined && params.contentType !== null) {
field = "contentType";
value = params.contentType;
}
this.setState({loading: true});
SessionBackend.getSessions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};
}
export default SessionListPage;

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 {Tag, Tooltip, message} from "antd"; import {Tag, Tooltip, message, theme} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons"; import {QuestionCircleTwoTone} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n"; import "./i18n";
@ -43,6 +43,13 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
{label: "Русский", key: "ru", country: "RU", alt: "Русский"}, {label: "Русский", key: "ru", country: "RU", alt: "Русский"},
]; ];
const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme;
export const Themes = [{label: i18next.t("general:Dark"), key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`},
{label: i18next.t("general:Compact"), key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`},
{label: i18next.t("general:Default"), key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`},
];
export const OtherProviderInfo = { export const OtherProviderInfo = {
SMS: { SMS: {
"Aliyun SMS": { "Aliyun SMS": {
@ -562,6 +569,14 @@ export function getAvatarColor(s) {
return colorList[hash % 4]; return colorList[hash % 4];
} }
export function getLogo(theme) {
if (theme === "Dark") {
return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
} else {
return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
}
}
export function getLanguageText(text) { export function getLanguageText(text) {
if (!text.includes("|")) { if (!text.includes("|")) {
return text; return text;
@ -587,6 +602,11 @@ export function setLanguage(language) {
i18next.changeLanguage(language); i18next.changeLanguage(language);
} }
export function setTheme(themeKey) {
localStorage.setItem("theme", themeKey);
dispatchEvent(new Event("themeChange"));
}
export function getAcceptLanguage() { export function getAcceptLanguage() {
if (i18next.language === null || i18next.language === "") { if (i18next.language === null || i18next.language === "") {
return "en;q=0.9,en;q=0.8"; return "en;q=0.9,en;q=0.8";
@ -799,6 +819,9 @@ export function renderLoginLink(application, text) {
export function redirectToLoginPage(application, history) { export function redirectToLoginPage(application, history) {
const loginLink = getLoginLink(application); const loginLink = getLoginLink(application);
if (loginLink.indexOf("http") === 0 || loginLink.indexOf("https") === 0) {
window.location.replace(loginLink);
}
history.push(loginLink); history.push(loginLink);
} }

View File

@ -288,6 +288,13 @@ class SyncerListPage 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

@ -253,6 +253,13 @@ class TokenListPage 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

@ -28,11 +28,7 @@ import SamlWidget from "./common/SamlWidget";
import SelectRegionBox from "./SelectRegionBox"; import SelectRegionBox from "./SelectRegionBox";
import WebAuthnCredentialTable from "./WebauthnCredentialTable"; import WebAuthnCredentialTable from "./WebauthnCredentialTable";
import ManagedAccountTable from "./ManagedAccountTable"; import ManagedAccountTable from "./ManagedAccountTable";
import PropertyTable from "./propertyTable";
import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const {Option} = Select; const {Option} = Select;
@ -490,13 +486,10 @@ class UserEditPage extends React.Component {
return ( return (
<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}>
{i18next.t("user:Properties")}: {Setting.getLabel(i18next.t("user:Properties"), i18next.t("user:Properties - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<CodeMirror <PropertyTable properties={this.state.user.properties} onUpdateTable={(value) => {this.updateUserField("properties", value);}} />
value={JSON.stringify(this.state.user.properties, null, 4)}
options={{mode: "javascript", theme: "material-darker"}}
/>
</Col> </Col>
</Row> </Row>
); );
@ -507,7 +500,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch disabled={this.state.user.owner === "built-in"} checked={this.state.user.isAdmin} onChange={checked => { <Switch disabled={disabled} checked={this.state.user.isAdmin} onChange={checked => {
this.updateUserField("isAdmin", checked); this.updateUserField("isAdmin", checked);
}} /> }} />
</Col> </Col>
@ -520,7 +513,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch disabled={this.state.user.owner === "built-in"} checked={this.state.user.isGlobalAdmin} onChange={checked => { <Switch disabled={disabled} checked={this.state.user.isGlobalAdmin} onChange={checked => {
this.updateUserField("isGlobalAdmin", checked); this.updateUserField("isGlobalAdmin", checked);
}} /> }} />
</Col> </Col>

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, Upload} from "antd"; import {Button, Popconfirm, Result, Switch, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@ -38,6 +38,7 @@ class UserListPage extends BaseListPage {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }
@ -369,6 +370,17 @@ class UserListPage 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 or logged in status invalid.")}
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={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
@ -411,6 +423,13 @@ class UserListPage extends BaseListPage {
if (users.length > 0) { if (users.length > 0) {
this.getOrganization(users[0].owner); this.getOrganization(users[0].owner);
} }
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
} else { } else {
@ -432,6 +451,13 @@ class UserListPage extends BaseListPage {
if (users.length > 0) { if (users.length > 0) {
this.getOrganization(users[0].owner); this.getOrganization(users[0].owner);
} }
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
} }

View File

@ -253,6 +253,13 @@ class WebhookListPage 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

@ -20,6 +20,7 @@ import * as Util from "./Util";
import {authConfig} from "./Auth"; import {authConfig} from "./Auth";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import i18next from "i18next"; import i18next from "i18next";
import RedirectForm from "../common/RedirectForm";
class AuthCallback extends React.Component { class AuthCallback extends React.Component {
constructor(props) { constructor(props) {
@ -27,6 +28,9 @@ class AuthCallback extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
msg: null, msg: null,
samlResponse: "",
relayState: "",
redirectUrl: "",
}; };
} }
@ -164,9 +168,17 @@ class AuthCallback extends React.Component {
const from = innerParams.get("from"); const from = innerParams.get("from");
Setting.goToLinkSoft(this, from); Setting.goToLinkSoft(this, from);
} else if (responseType === "saml") { } else if (responseType === "saml") {
const SAMLResponse = res.data; if (res.data2.method === "POST") {
const redirectUri = res.data2; this.setState({
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); samlResponse: res.data,
redirectUrl: res.data2.redirectUrl,
relayState: oAuthParams.relayState,
});
} else {
const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
} }
} else { } else {
this.setState({ this.setState({
@ -177,8 +189,12 @@ class AuthCallback extends React.Component {
} }
render() { render() {
if (this.state.samlResponse !== "") {
return <RedirectForm samlResponse={this.state.samlResponse} redirectUrl={this.state.redirectUrl} relayState={this.state.relayState} />;
}
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {
(this.state.msg === null) ? ( (this.state.msg === null) ? (
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Spin} from "antd"; import {Card, Spin} from "antd";
import {withRouter} from "react-router-dom"; import {withRouter} from "react-router-dom";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
@ -39,7 +39,8 @@ class CasLogout extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", "Logged out successfully"); Setting.showMessage("success", "Logged out successfully");
this.props.clearAccount(); this.props.onUpdateAccount(null);
this.onUpdateApplication(null);
const redirectUri = res.data2; const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
@ -49,6 +50,7 @@ class CasLogout extends React.Component {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`); Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
} }
} else { } else {
this.onUpdateApplication(null);
Setting.showMessage("error", `Failed to log out: ${res.msg}`); Setting.showMessage("error", `Failed to log out: ${res.msg}`);
} }
}); });
@ -57,11 +59,13 @@ class CasLogout extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <Card>
{ <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} /> {
} <Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
</div> }
</div>
</Card>
); );
} }
} }

View File

@ -34,12 +34,7 @@ class ForgetPage extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
account: props.account, account: props.account,
applicationName: applicationName: props.applicationName ?? props.match.params?.applicationName,
props.applicationName !== undefined
? props.applicationName
: props.match === undefined
? null
: props.match.params.applicationName,
application: null, application: null,
msg: null, msg: null,
userId: "", userId: "",
@ -57,34 +52,36 @@ class ForgetPage extends React.Component {
}; };
} }
UNSAFE_componentWillMount() { componentDidMount() {
if (this.state.applicationName !== undefined) { if (this.getApplicationObj() === null) {
this.getApplication(); if (this.state.applicationName !== undefined) {
} else { this.getApplication();
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type); } else {
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type);
}
} }
} }
getApplication() { getApplication() {
if (this.state.applicationName === null) { if (this.state.applicationName === undefined) {
return; return;
} }
ApplicationBackend.getApplication("admin", this.state.applicationName).then( ApplicationBackend.getApplication("admin", this.state.applicationName)
(application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
} });
);
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application; }
} else {
return this.state.application; onUpdateApplication(application) {
} this.props.onUpdateApplication(application);
} }
onFormFinish(name, info, forms) { onFormFinish(name, info, forms) {
@ -143,7 +140,7 @@ class ForgetPage extends React.Component {
username: this.state.username, username: this.state.username,
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.getApplicationObj()?.organizationObj.phonePrefix,
type: "login", type: "login",
}, oAuthParams).then(res => { }, oAuthParams).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
@ -166,10 +163,10 @@ class ForgetPage extends React.Component {
onFinish(values) { onFinish(values) {
values.username = this.state.username; values.username = this.state.username;
values.userOwner = this.state.application?.organizationObj.name; values.userOwner = this.getApplicationObj()?.organizationObj.name;
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.redirectToLoginPage(this.state.application, this.props.history); Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
} else { } else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
} }
@ -356,14 +353,14 @@ class ForgetPage extends React.Component {
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
method={"forget"} method={"forget"}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.state.application), this.state.name]} onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
application={application} application={application}
/> />
) : ( ) : (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
method={"forget"} method={"forget"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.state.application), this.state.name]} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
application={application} application={application}
/> />
)} )}
@ -492,7 +489,7 @@ class ForgetPage extends React.Component {
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}> <div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
<Row> <Row>
@ -550,7 +547,7 @@ class ForgetPage extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
</div> </React.Fragment>
); );
} }
} }

View File

@ -61,19 +61,19 @@ class LoginPage extends React.Component {
} }
} }
UNSAFE_componentWillMount() {
if (this.state.type === "login" || this.state.type === "cas") {
this.getApplication();
} else if (this.state.type === "code") {
this.getApplicationLogin();
} else if (this.state.type === "saml") {
this.getSamlApplication();
} else {
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
}
}
componentDidMount() { componentDidMount() {
if (this.getApplicationObj() === null) {
if (this.state.type === "login" || this.state.type === "cas") {
this.getApplication();
} else if (this.state.type === "code") {
this.getApplicationLogin();
} else if (this.state.type === "saml") {
this.getSamlApplication();
} else {
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
}
}
Setting.Countries.forEach((country) => { Setting.Countries.forEach((country) => {
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`; new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
}); });
@ -96,11 +96,13 @@ class LoginPage extends React.Component {
AuthBackend.getApplicationLogin(oAuthParams) AuthBackend.getApplicationLogin(oAuthParams)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.onUpdateApplication(res.data);
this.setState({ this.setState({
application: res.data, application: res.data,
}); });
} else { } else {
// Setting.showMessage("error", res.msg); // Setting.showMessage("error", res.msg);
this.onUpdateApplication(null);
this.setState({ this.setState({
application: res.data, application: res.data,
msg: res.msg, msg: res.msg,
@ -117,6 +119,7 @@ class LoginPage extends React.Component {
if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") { if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -125,11 +128,13 @@ class LoginPage extends React.Component {
OrganizationBackend.getDefaultApplication("admin", this.state.owner) OrganizationBackend.getDefaultApplication("admin", this.state.owner)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.onUpdateApplication(res.data);
this.setState({ this.setState({
application: res.data, application: res.data,
applicationName: res.data.name, applicationName: res.data.name,
}); });
} else { } else {
this.onUpdateApplication(null);
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
}); });
@ -142,25 +147,25 @@ class LoginPage extends React.Component {
} }
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName) ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
} });
);
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application;
} else {
return this.state.application;
}
} }
onUpdateAccount(account) { onUpdateAccount(account) {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) { parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) { if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto"; return "0 auto";
@ -191,8 +196,8 @@ class LoginPage extends React.Component {
values["relayState"] = oAuthParams.relayState; values["relayState"] = oAuthParams.relayState;
} }
if (this.state.application.organization !== null && this.state.application.organization !== undefined) { if (this.getApplicationObj()?.organization) {
values["organization"] = this.state.application.organization; values["organization"] = this.getApplicationObj().organization;
} }
} }
postCodeLoginAction(res) { postCodeLoginAction(res) {
@ -315,15 +320,15 @@ class LoginPage extends React.Component {
const accessToken = res.data; const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`); Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "saml") { } else if (responseType === "saml") {
const SAMLResponse = res.data; if (res.data2.method === "POST") {
const redirectUri = res.data2;
if (this.state.application.assertionConsumerUrl !== "") {
this.setState({ this.setState({
samlResponse: res.data, samlResponse: res.data,
redirectUrl: res.data2, redirectUrl: res.data2.redirectUrl,
relayState: oAuthParams.relayState, relayState: oAuthParams.relayState,
}); });
} else { } else {
const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
} }
} }
@ -573,12 +578,12 @@ class LoginPage extends React.Component {
<span style={{float: "right"}}> <span style={{float: "right"}}>
{ {
!application.enableSignUp ? null : ( !application.enableSignUp ? null : (
<> <React.Fragment>
{i18next.t("login:No account?")}&nbsp; {i18next.t("login:No account?")}&nbsp;
{ {
Setting.renderSignupLink(application, i18next.t("login:sign up now")) Setting.renderSignupLink(application, i18next.t("login:sign up now"))
} }
</> </React.Fragment>
) )
} }
</span> </span>
@ -611,13 +616,13 @@ class LoginPage extends React.Component {
this.sendSilentSigninData("signing-in"); this.sendSilentSigninData("signing-in");
const values = {}; const values = {};
values["application"] = this.state.application.name; values["application"] = application.name;
this.onFinish(values); this.onFinish(values);
} }
if (application.enableAutoSignin) { if (application.enableAutoSignin) {
const values = {}; const values = {};
values["application"] = this.state.application.name; values["application"] = application.name;
this.onFinish(values); this.onFinish(values);
} }
@ -632,7 +637,7 @@ class LoginPage extends React.Component {
<br /> <br />
<SelfLoginButton account={this.props.account} onClick={() => { <SelfLoginButton account={this.props.account} onClick={() => {
const values = {}; const values = {};
values["application"] = this.state.application.name; values["application"] = application.name;
this.onFinish(values); this.onFinish(values);
}} /> }} />
<br /> <br />
@ -788,16 +793,16 @@ class LoginPage extends React.Component {
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) { if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup")); Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
</div> </div>
); );
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}> <div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />} {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel"> <div className="login-panel">
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}> <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
@ -827,7 +832,7 @@ class LoginPage extends React.Component {
</div> </div>
</div> </div>
</div> </div>
</div> </React.Fragment>
); );
} }
} }

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, Result, Row} from "antd"; import {Button, Card, Col, Result, Row} from "antd";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as UserBackend from "../backend/UserBackend"; import * as UserBackend from "../backend/UserBackend";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
@ -30,7 +30,7 @@ class PromptPage extends React.Component {
this.state = { this.state = {
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 ?? (props.match === undefined ? null : props.match.params.applicationName),
application: null, application: null,
user: null, user: null,
}; };
@ -38,7 +38,9 @@ class PromptPage extends React.Component {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getUser(); this.getUser();
this.getApplication(); if (this.getApplicationObj() === null) {
this.getApplication();
}
} }
getUser() { getUser() {
@ -59,6 +61,7 @@ class PromptPage extends React.Component {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -66,11 +69,11 @@ class PromptPage extends React.Component {
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application; }
} else {
return this.state.application; onUpdateApplication(application) {
} this.props.onUpdateApplication(application);
} }
parseUserField(key, value) { parseUserField(key, value) {
@ -122,7 +125,7 @@ class PromptPage extends React.Component {
renderContent(application) { renderContent(application) {
return ( return (
<div style={{width: "400px"}}> <div style={{width: "500px"}}>
{ {
this.renderAffiliation(application) this.renderAffiliation(application)
} }
@ -247,9 +250,9 @@ class PromptPage extends React.Component {
} }
return ( return (
<Row> <div style={{display: "flex", flex: "1", justifyContent: "center"}}>
<Col span={24} style={{display: "flex", justifyContent: "center"}}> <Card>
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center"}}> <div style={{marginTop: "30px", marginBottom: "30px", textAlign: "center"}}>
{ {
Setting.renderHelmet(application) Setting.renderHelmet(application)
} }
@ -259,16 +262,12 @@ class PromptPage extends React.Component {
{ {
this.renderContent(application) this.renderContent(application)
} }
<Row style={{margin: 10}}>
<Col span={18}>
</Col>
</Row>
<div style={{marginTop: "50px"}}> <div style={{marginTop: "50px"}}>
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button> <Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
</div> </div>
</div> </div>
</Col> </Card>
</Row> </div>
); );
} }
} }

View File

@ -95,7 +95,7 @@ class SamlCallback extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {
(this.state.msg === null) ? ( (this.state.msg === null) ? (
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />

View File

@ -22,7 +22,6 @@ class SelfForgetPage extends React.Component {
<ForgetPage <ForgetPage
type={"forgotPassword"} type={"forgotPassword"}
applicationName={authConfig.appName} applicationName={authConfig.appName}
account={this.props.account}
{...this.props} {...this.props}
/> />
); );

View File

@ -19,7 +19,7 @@ import {authConfig} from "./Auth";
class SelfLoginPage extends React.Component { class SelfLoginPage extends React.Component {
render() { render() {
return ( return (
<LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} account={this.props.account} {...this.props} /> <LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} {...this.props} />
); );
} }
} }

View File

@ -65,7 +65,7 @@ class SignupPage extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
applicationName: props.match?.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName, applicationName: props.match.params?.applicationName ?? authConfig.appName,
application: null, application: null,
email: "", email: "",
phone: "", phone: "",
@ -91,10 +91,12 @@ class SignupPage extends React.Component {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
} }
if (applicationName !== undefined) { if (this.getApplicationObj() === null) {
this.getApplication(applicationName); if (applicationName !== undefined) {
} else { this.getApplication(applicationName);
Setting.showMessage("error", `Unknown application name: ${applicationName}`); } else {
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
}
} }
} }
@ -105,6 +107,7 @@ class SignupPage extends React.Component {
ApplicationBackend.getApplication("admin", applicationName) ApplicationBackend.getApplication("admin", applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -128,11 +131,7 @@ class SignupPage extends React.Component {
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application;
} else {
return this.state.application;
}
} }
getTermsofuseContent(url) { getTermsofuseContent(url) {
@ -149,6 +148,10 @@ class SignupPage extends React.Component {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) { parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) { if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto"; return "0 auto";
@ -632,9 +635,9 @@ class SignupPage extends React.Component {
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}> <div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />} {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel" > <div className="login-panel" >
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}> <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
@ -657,7 +660,7 @@ class SignupPage extends React.Component {
{ {
this.renderModal() this.renderModal()
} }
</div> </React.Fragment>
); );
} }
} }

View File

@ -0,0 +1,36 @@
// 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.
import * as Setting from "../Setting";
export function getSessions(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-sessions?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function deleteSession(session) {
return fetch(`${Setting.ServerUrl}/api/delete-session`, {
method: "POST",
credentials: "include",
body: JSON.stringify(session),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@ -147,7 +147,7 @@ class OAuthWidget extends React.Component {
</Col> </Col>
<Col span={24 - this.props.labelSpan} > <Col span={24 - this.props.labelSpan} >
<img style={{marginRight: "10px"}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" /> <img style={{marginRight: "10px"}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
<span style={{width: this.props.labelSpan === 3 ? "300px" : "130px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}> <span style={{width: this.props.labelSpan === 3 ? "300px" : "200px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}>
{ {
linkedValue === "" ? ( linkedValue === "" ? (
"(empty)" "(empty)"

View File

@ -293,14 +293,15 @@ class PolicyTable extends React.Component {
} }
render() { render() {
return (<> return (
<Button type="primary" disabled={this.state.editingIndex !== ""} onClick={() => {this.synPolicies();}}> <React.Fragment>
{i18next.t("adapter:Sync")} <Button type="primary" disabled={this.state.editingIndex !== ""} onClick={() => {this.synPolicies();}}>
</Button> {i18next.t("adapter:Sync")}
{ </Button>
this.renderTable(this.state.policyLists) {
} this.renderTable(this.state.policyLists)
</> }
</React.Fragment>
); );
} }
} }

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import i18next from "i18next";
export const RedirectForm = (props) => { export const RedirectForm = (props) => {
@ -20,23 +21,24 @@ export const RedirectForm = (props) => {
document.getElementById("saml").submit(); document.getElementById("saml").submit();
}, []); }, []);
return (<> return (
<p>Redirecting, please wait.</p> <React.Fragment>
<form id="saml" method="post" action={props.redirectUrl}> <p>{i18next.t("login:Redirecting, please wait.")}</p>
<input <form id="saml" method="post" action={props.redirectUrl}>
type="hidden" <input
name="SAMLResponse" type="hidden"
id="samlResponse" name="SAMLResponse"
value={props.samlResponse} id="samlResponse"
/> value={props.samlResponse}
<input />
type="hidden" <input
name="RelayState" type="hidden"
id="relayState" name="RelayState"
value={props.relayState} id="relayState"
/> value={props.relayState}
</form> />
</> </form>
</React.Fragment>
); );
}; };

View File

@ -28,7 +28,7 @@ code {
.logo { .logo {
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png"); background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
background-size: 130px, 27px; background-size: 130px, 27px !important;
width: 130px; width: 130px;
height: 27px; height: 27px;
margin: 17px 0 16px 15px; margin: 17px 0 16px 15px;
@ -45,7 +45,3 @@ code {
.ant-list-sm .ant-list-item { .ant-list-sm .ant-list-item {
padding: 2px !important; padding: 2px !important;
} }
.ant-layout-header {
background-color: white !important;
}

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client-IP", "Client IP": "Client-IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Erstellte Zeit", "Created time": "Erstellte Zeit",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Speichern", "Save": "Speichern",
"Save & Exit": "Speichern & Beenden", "Save & Exit": "Speichern & Beenden",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Anmelde-URL", "Signin URL": "Anmelde-URL",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "Registrierungs-URL", "Signup URL": "Registrierungs-URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Die von Ihnen besuchte Seite existiert leider nicht.", "Sorry, the page you visited does not exist.": "Die von Ihnen besuchte Seite existiert leider nicht.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -299,6 +304,7 @@
"Continue with": "Weiter mit", "Continue with": "Weiter mit",
"Email or phone": "E-Mail oder Telefon", "Email or phone": "E-Mail oder Telefon",
"Forgot password?": "Passwort vergessen?", "Forgot password?": "Passwort vergessen?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "Kein Konto?", "No account?": "Kein Konto?",
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an", "Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
@ -308,6 +314,7 @@
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!", "Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
"Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!", "Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!",
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!", "Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Anmelden", "Sign In": "Anmelden",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Mit {type} anmelden", "Sign in with {type}": "Mit {type} anmelden",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Ist globaler Admin", "Is global admin": "Ist globaler Admin",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Standort", "Location": "Standort",
"Location - Tooltip": "Standort - Tooltip", "Location - Tooltip": "Standort - Tooltip",
@ -736,6 +744,7 @@
"Password Set": "Passwort setzen", "Password Set": "Passwort setzen",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Eigenschaften", "Properties": "Eigenschaften",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Neu erneut eingeben", "Re-enter New": "Neu erneut eingeben",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Telefon zurücksetzen...", "Reset Phone...": "Telefon zurücksetzen...",
@ -751,6 +760,7 @@
"Unlink": "Link aufheben", "Unlink": "Link aufheben",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Foto hochladen", "Upload a photo": "Foto hochladen",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "Passwort eingeben" "input password": "Passwort eingeben"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Created time", "Created time": "Created time",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Save", "Save": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Signin URL", "Signin URL": "Signin URL",
"Signin URL - Tooltip": "Signin URL - Tooltip", "Signin URL - Tooltip": "Signin URL - Tooltip",
"Signup URL": "Signup URL", "Signup URL": "Signup URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.", "Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -299,6 +304,7 @@
"Continue with": "Continue with", "Continue with": "Continue with",
"Email or phone": "Email or phone", "Email or phone": "Email or phone",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "No account?", "No account?": "No account?",
"Or sign in with another account": "Or sign in with another account", "Or sign in with another account": "Or sign in with another account",
@ -308,6 +314,7 @@
"Please input your password!": "Please input your password!", "Please input your password!": "Please input your password!",
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!", "Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
"Please input your username, Email or phone!": "Please input your username, Email or phone!", "Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In", "Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}", "Sign in with {type}": "Sign in with {type}",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "Is forbidden - Tooltip", "Is forbidden - Tooltip": "Is forbidden - Tooltip",
"Is global admin": "Is global admin", "Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is global admin - Tooltip", "Is global admin - Tooltip": "Is global admin - Tooltip",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "Location - Tooltip", "Location - Tooltip": "Location - Tooltip",
@ -736,6 +744,7 @@
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...", "Reset Phone...": "Reset Phone...",
@ -751,6 +760,7 @@
"Unlink": "Unlink", "Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo", "Upload a photo": "Upload a photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "input password" "input password": "input password"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "IP du client", "Client IP": "IP du client",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Date de création", "Created time": "Date de création",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Avatar par défaut", "Default avatar": "Avatar par défaut",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Enregistrer", "Save": "Enregistrer",
"Save & Exit": "Enregistrer & Quitter", "Save & Exit": "Enregistrer & Quitter",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "URL de connexion", "Signin URL": "URL de connexion",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "URL d'inscription", "Signup URL": "URL d'inscription",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.", "Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Désolé, vous n'avez pas la permission d'accéder à cette page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -299,6 +304,7 @@
"Continue with": "Continuer avec", "Continue with": "Continuer avec",
"Email or phone": "Courriel ou téléphone", "Email or phone": "Courriel ou téléphone",
"Forgot password?": "Mot de passe oublié ?", "Forgot password?": "Mot de passe oublié ?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "Pas de compte ?", "No account?": "Pas de compte ?",
"Or sign in with another account": "Ou connectez-vous avec un autre compte", "Or sign in with another account": "Ou connectez-vous avec un autre compte",
@ -308,6 +314,7 @@
"Please input your password!": "Veuillez saisir votre mot de passe !", "Please input your password!": "Veuillez saisir votre mot de passe !",
"Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !", "Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !",
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!", "Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Se connecter", "Sign In": "Se connecter",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Se connecter avec {type}", "Sign in with {type}": "Se connecter avec {type}",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Est un administrateur global", "Is global admin": "Est un administrateur global",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Lier", "Link": "Lier",
"Location": "Localisation", "Location": "Localisation",
"Location - Tooltip": "Localisation - Infobulle", "Location - Tooltip": "Localisation - Infobulle",
@ -736,6 +744,7 @@
"Password Set": "Mot de passe défini", "Password Set": "Mot de passe défini",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Propriétés", "Properties": "Propriétés",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Nouvelle entrée", "Re-enter New": "Nouvelle entrée",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Réinitialiser le téléphone...", "Reset Phone...": "Réinitialiser le téléphone...",
@ -751,6 +760,7 @@
"Unlink": "Délier", "Unlink": "Délier",
"Upload (.xlsx)": "Télécharger (.xlsx)", "Upload (.xlsx)": "Télécharger (.xlsx)",
"Upload a photo": "Télécharger une photo", "Upload a photo": "Télécharger une photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "saisir le mot de passe" "input password": "saisir le mot de passe"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "クライアント IP", "Client IP": "クライアント IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "作成日時", "Created time": "作成日時",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "デフォルトのアバター", "Default avatar": "デフォルトのアバター",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "保存", "Save": "保存",
"Save & Exit": "保存して終了", "Save & Exit": "保存して終了",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "サインインURL", "Signin URL": "サインインURL",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "サインアップURL", "Signup URL": "サインアップURL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "申し訳ありませんが、訪問したページは存在しません。", "Sorry, the page you visited does not exist.": "申し訳ありませんが、訪問したページは存在しません。",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "申し訳ありませんが、このページにアクセスする権限がありません。", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -299,6 +304,7 @@
"Continue with": "次で続ける", "Continue with": "次で続ける",
"Email or phone": "Eメールまたは電話番号", "Email or phone": "Eメールまたは電話番号",
"Forgot password?": "パスワードを忘れましたか?", "Forgot password?": "パスワードを忘れましたか?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "アカウントがありませんか?", "No account?": "アカウントがありませんか?",
"Or sign in with another account": "または別のアカウントでサインイン", "Or sign in with another account": "または別のアカウントでサインイン",
@ -308,6 +314,7 @@
"Please input your password!": "パスワードを入力してください!", "Please input your password!": "パスワードを入力してください!",
"Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください", "Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください",
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。", "Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "サインイン", "Sign In": "サインイン",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "{type} でサインイン", "Sign in with {type}": "{type} でサインイン",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "グローバル管理者", "Is global admin": "グローバル管理者",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "リンク", "Link": "リンク",
"Location": "場所", "Location": "場所",
"Location - Tooltip": "場所 → ツールチップ", "Location - Tooltip": "場所 → ツールチップ",
@ -736,6 +744,7 @@
"Password Set": "パスワード設定", "Password Set": "パスワード設定",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "プロパティー", "Properties": "プロパティー",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "新しい入力", "Re-enter New": "新しい入力",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "電話番号をリセット...", "Reset Phone...": "電話番号をリセット...",
@ -751,6 +760,7 @@
"Unlink": "リンクを解除", "Unlink": "リンクを解除",
"Upload (.xlsx)": "アップロード (.xlsx)", "Upload (.xlsx)": "アップロード (.xlsx)",
"Upload a photo": "写真をアップロード", "Upload a photo": "写真をアップロード",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "パスワードを入力" "input password": "パスワードを入力"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Created time", "Created time": "Created time",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Save", "Save": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Signin URL", "Signin URL": "Signin URL",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "Signup URL", "Signup URL": "Signup URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.", "Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -299,6 +304,7 @@
"Continue with": "Continue with", "Continue with": "Continue with",
"Email or phone": "Email or phone", "Email or phone": "Email or phone",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "No account?", "No account?": "No account?",
"Or sign in with another account": "Or sign in with another account", "Or sign in with another account": "Or sign in with another account",
@ -308,6 +314,7 @@
"Please input your password!": "Please input your password!", "Please input your password!": "Please input your password!",
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!", "Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
"Please input your username, Email or phone!": "Please input your username, Email or phone!", "Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In", "Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}", "Sign in with {type}": "Sign in with {type}",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Is global admin", "Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "Location - Tooltip", "Location - Tooltip": "Location - Tooltip",
@ -736,6 +744,7 @@
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...", "Reset Phone...": "Reset Phone...",
@ -751,6 +760,7 @@
"Unlink": "Unlink", "Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo", "Upload a photo": "Upload a photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "input password" "input password": "input password"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Нажмите здесь, чтобы загрузить", "Click to Upload": "Нажмите здесь, чтобы загрузить",
"Client IP": "IP клиента", "Client IP": "IP клиента",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Время создания", "Created time": "Время создания",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Аватар по умолчанию", "Default avatar": "Аватар по умолчанию",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Сохранить", "Save": "Сохранить",
"Save & Exit": "Сохранить и выйти", "Save & Exit": "Сохранить и выйти",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "URL входа", "Signin URL": "URL входа",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "URL регистрации", "Signup URL": "URL регистрации",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Извините, посещенная вами страница не существует.", "Sorry, the page you visited does not exist.": "Извините, посещенная вами страница не существует.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Извините, вы не имеете права доступа к этой странице.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -299,6 +304,7 @@
"Continue with": "Продолжить с", "Continue with": "Продолжить с",
"Email or phone": "Электронная почта или телефон", "Email or phone": "Электронная почта или телефон",
"Forgot password?": "Забыли пароль?", "Forgot password?": "Забыли пароль?",
"Loading": "Loading",
"Logging out...": "Выход...", "Logging out...": "Выход...",
"No account?": "Нет учетной записи?", "No account?": "Нет учетной записи?",
"Or sign in with another account": "Или войти с помощью другой учетной записи", "Or sign in with another account": "Или войти с помощью другой учетной записи",
@ -308,6 +314,7 @@
"Please input your password!": "Пожалуйста, введите ваш пароль!", "Please input your password!": "Пожалуйста, введите ваш пароль!",
"Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!", "Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!",
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!", "Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Войти", "Sign In": "Войти",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Войти с помощью {type}", "Sign in with {type}": "Войти с помощью {type}",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Глобальный администратор", "Is global admin": "Глобальный администратор",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Ссылка", "Link": "Ссылка",
"Location": "Местоположение", "Location": "Местоположение",
"Location - Tooltip": "Расположение - Подсказка", "Location - Tooltip": "Расположение - Подсказка",
@ -736,6 +744,7 @@
"Password Set": "Пароль установлен", "Password Set": "Пароль установлен",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Свойства", "Properties": "Свойства",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Введите еще раз", "Re-enter New": "Введите еще раз",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Сбросить телефон...", "Reset Phone...": "Сбросить телефон...",
@ -751,6 +760,7 @@
"Unlink": "Отвязать", "Unlink": "Отвязать",
"Upload (.xlsx)": "Загрузить (.xlsx)", "Upload (.xlsx)": "Загрузить (.xlsx)",
"Upload a photo": "Загрузить фото", "Upload a photo": "Загрузить фото",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "пароль для ввода" "input password": "пароль для ввода"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "点击上传", "Click to Upload": "点击上传",
"Client IP": "客户端IP", "Client IP": "客户端IP",
"Close": "关闭", "Close": "关闭",
"Compact": "紧凑",
"Created time": "创建时间", "Created time": "创建时间",
"Dark": "黑暗",
"Default": "默认",
"Default application": "默认应用", "Default application": "默认应用",
"Default application - Tooltip": "默认应用", "Default application - Tooltip": "默认应用",
"Default avatar": "默认头像", "Default avatar": "默认头像",
@ -233,6 +236,8 @@
"Roles - Tooltip": "角色", "Roles - Tooltip": "角色",
"Save": "保存", "Save": "保存",
"Save & Exit": "保存 & 退出", "Save & Exit": "保存 & 退出",
"Session ID": "会话 ID",
"Sessions": "会话",
"Signin URL": "登录URL", "Signin URL": "登录URL",
"Signin URL - Tooltip": "用户的登录地址", "Signin URL - Tooltip": "用户的登录地址",
"Signup URL": "注册URL", "Signup URL": "注册URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "表示用户注册时通过哪个应用注册的", "Signup application - Tooltip": "表示用户注册时通过哪个应用注册的",
"Sorry, the page you visited does not exist.": "抱歉,您访问的页面不存在", "Sorry, the page you visited does not exist.": "抱歉,您访问的页面不存在",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "抱歉,您访问的用户不存在或您无权访问该用户", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "抱歉,您访问的用户不存在或您无权访问该用户",
"Sorry, you do not have permission to access this page.": "抱歉,您无权访问该页面", "Sorry, you do not have permission to access this page or logged in status invalid.": "抱歉,您无权访问该页面或登录状态失效",
"State": "状态", "State": "状态",
"State - Tooltip": "状态", "State - Tooltip": "状态",
"Successfully added": "添加成功", "Successfully added": "添加成功",
@ -299,6 +304,7 @@
"Continue with": "使用以下账号继续", "Continue with": "使用以下账号继续",
"Email or phone": "Email或手机号", "Email or phone": "Email或手机号",
"Forgot password?": "忘记密码?", "Forgot password?": "忘记密码?",
"Loading": "加载中",
"Logging out...": "正在退出登录...", "Logging out...": "正在退出登录...",
"No account?": "没有账号?", "No account?": "没有账号?",
"Or sign in with another account": "或者,登录其他账号", "Or sign in with another account": "或者,登录其他账号",
@ -308,6 +314,7 @@
"Please input your password!": "请输入您的密码!", "Please input your password!": "请输入您的密码!",
"Please input your password, at least 6 characters!": "请输入您的密码不少于6位", "Please input your password, at least 6 characters!": "请输入您的密码不少于6位",
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号", "Please input your username, Email or phone!": "请输入您的用户名、Email或手机号",
"Redirecting, please wait.": "正在跳转, 请稍等.",
"Sign In": "登录", "Sign In": "登录",
"Sign in with WebAuthn": "WebAuthn登录", "Sign in with WebAuthn": "WebAuthn登录",
"Sign in with {type}": "{type}登录", "Sign in with {type}": "{type}登录",
@ -721,6 +728,7 @@
"Is forbidden - Tooltip": "账户是否已被禁用", "Is forbidden - Tooltip": "账户是否已被禁用",
"Is global admin": "是全局管理员", "Is global admin": "是全局管理员",
"Is global admin - Tooltip": "是应用程序管理员", "Is global admin - Tooltip": "是应用程序管理员",
"Keys": "键",
"Link": "绑定", "Link": "绑定",
"Location": "城市", "Location": "城市",
"Location - Tooltip": "居住地址所在的城市", "Location - Tooltip": "居住地址所在的城市",
@ -736,6 +744,7 @@
"Password Set": "密码已设置", "Password Set": "密码已设置",
"Please select avatar from resources": "从资源中选择...", "Please select avatar from resources": "从资源中选择...",
"Properties": "属性", "Properties": "属性",
"Properties - Tooltip": "属性",
"Re-enter New": "重复新密码", "Re-enter New": "重复新密码",
"Reset Email...": "重置邮箱...", "Reset Email...": "重置邮箱...",
"Reset Phone...": "重置手机号...", "Reset Phone...": "重置手机号...",
@ -751,6 +760,7 @@
"Unlink": "解绑", "Unlink": "解绑",
"Upload (.xlsx)": "上传(.xlsx", "Upload (.xlsx)": "上传(.xlsx",
"Upload a photo": "上传头像", "Upload a photo": "上传头像",
"Values": "值",
"WebAuthn credentials": "WebAuthn凭据", "WebAuthn credentials": "WebAuthn凭据",
"input password": "输入密码" "input password": "输入密码"
}, },

131
web/src/propertyTable.js Normal file
View File

@ -0,0 +1,131 @@
// 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.
import React from "react";
import {Button, Input, Table} from "antd";
import i18next from "i18next";
import {DeleteOutlined} from "@ant-design/icons";
import * as Setting from "./Setting";
class PropertyTable extends React.Component {
constructor(props) {
super(props);
this.state = {
properties: [],
count: Object.entries(this.props.properties).length,
};
// transfer the Object to object[]
Object.entries(this.props.properties).map((item, index) => {
this.state.properties.push({key: index, name: item[0], value: item[1]});
});
}
page = 1;
updateTable(table) {
this.setState({properties: table});
const properties = {};
table.map((item) => {
properties[item.name] = item.value;
});
this.props.onUpdateTable(properties);
}
addRow(table) {
const row = {key: this.state.count, name: "", value: ""};
if (table === undefined) {
table = [];
}
table = Setting.addRow(table, row);
this.setState({count: this.state.count + 1});
this.updateTable(table);
}
deleteRow(table, index) {
table = Setting.deleteRow(table, this.getIndex(index));
this.updateTable(table);
}
getIndex(index) {
// Parameter is the row index in table, need to calculate the index in dataSource. 10 is the pageSize.
return index + (this.page - 1) * 10;
}
updateField(table, index, key, value) {
table[this.getIndex(index)][key] = value;
this.updateTable(table);
}
renderTable(table) {
const columns = [
{
title: i18next.t("user:Keys"),
dataIndex: "name",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "name", e.target.value);
}} />
);
},
},
{
title: i18next.t("user:Values"),
dataIndex: "value",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "value", e.target.value);
}} />
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "operation",
width: "20px",
render: (text, record, index) => {
return (
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
);
},
},
];
return (
<Table title={() => (
<div>
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
pagination={{onChange: page => {this.page = page;}}}
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
/>
);
}
render() {
return (
<React.Fragment>
{
this.renderTable(this.state.properties)
}
</React.Fragment>
);
}
}
export default PropertyTable;

View File

@ -17,17 +17,17 @@
dependencies: dependencies:
"@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/gen-mapping" "^0.1.0"
"@ant-design/cssinjs@^1.0.0":
version "1.1.0" "@jridgewell/trace-mapping" "^0.3.9"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.1.0.tgz#d7b5e0811e8241e66c2812c179bd7b7ef961f7ab"
integrity sha512-9kfWCnlcWZLMc184HL7zGUU3odKo/5HBMNxDxhSds2DoIzi/ojmmOU1A1butWVDSPcAbLyNQ85vxUI8mkkHrlA==
"@ant-design/colors@^6.0.0": "@ant-design/colors@^6.0.0":
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298" resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298"
rc-util "^5.24.2"
integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ== integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==
dependencies: dependencies:
@ -46,16 +46,16 @@
dependencies: dependencies:
"@ant-design/react-slick@~0.29.1": "@babel/runtime" "^7.11.1"
version "0.29.2"
resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.29.2.tgz#53e6a7920ea3562eebb304c15a7fc2d7e619d29c" "@emotion/hash" "^0.8.0"
integrity sha512-kgjtKmkGHa19FW21lHnAfyyH9AAoh35pBdcJ53rHmQ3O+cfFHGHnUbj/HFrRNJ5vIts09FKJVAD8RpaC+RaWfA==
"@emotion/unitless" "^0.7.5" "@emotion/unitless" "^0.7.5"
classnames "^2.3.1" classnames "^2.3.1"
lodash "^4.17.21"
csstype "^3.0.10" csstype "^3.0.10"
rc-util "^5.27.0" rc-util "^5.27.0"
stylis "^4.0.13" stylis "^4.0.13"
@ -1482,6 +1482,13 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-flow@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1"
@ -2112,10 +2119,10 @@
"@babel/plugin-transform-modules-umd@^7.18.6": "@babel/plugin-transform-modules-umd@^7.18.6":
"@rc-component/tour@~1.0.1-2":
version "1.0.1" version "7.18.6"
resolved "https://registry.yarnpkg.com/@rc-component/tour/-/tour-1.0.1.tgz#aa8ea890462eed43ee0fb33543995b342d87ee76"
integrity sha512-azbiWP0UwGDeWfGS7oCR0gHhbWJW7O6g4wFUaC19oY+eCjmY0RPY0u0p93BJ89D8NoxP9LhSSBuU/YQA5H5kbA== resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9"
integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==
@ -3141,57 +3148,57 @@ ansi-styles@^6.0.0:
antd@5.0.5: "@crowdin/cli@^3.7.10":
version "5.0.5"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.0.5.tgz#16f0ade8b2d2ea9f7bd47a8e0af81884dc504a7d" version "3.7.10"
integrity sha512-8jWUjZ65urNHZPg9/Ywa9V0PlNfqjhewKgSPF4nraN9X5v434lDJkRBQGN7meNixQ6aM2B/JhXPm9UaJ/tAQmA==
resolved "https://registry.yarnpkg.com/@crowdin/cli/-/cli-3.7.10.tgz#ee74da1f2b4d27a7050d7b6a3960835854b92b7d" resolved "https://registry.yarnpkg.com/@crowdin/cli/-/cli-3.7.10.tgz#ee74da1f2b4d27a7050d7b6a3960835854b92b7d"
"@ant-design/cssinjs" "^1.0.0" integrity sha512-L0sjeEv4bn7LHNYsKxl2aTrah16u1ThufN0xvMMH7o53lD29llvVfAD9jVOttSl/kyQ+mMDY8GLzjPRNFdLJqQ==
"@ant-design/react-slick" "~0.29.1" dependencies:
njre "^0.2.0" njre "^0.2.0"
"@rc-component/tour" "~1.0.1-2"
shelljs "^0.8.4" shelljs "^0.8.4"
lodash "^4.17.21"
rc-cascader "~3.7.0" "@cspotcode/source-map-support@^0.8.0":
version "0.8.1" version "0.8.1"
rc-drawer "~6.0.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
rc-image "~5.12.0"
dependencies: dependencies:
"@jridgewell/trace-mapping" "0.3.9" "@jridgewell/trace-mapping" "0.3.9"
rc-notification "~5.0.0-alpha.9"
"@csstools/normalize.css@*": "@csstools/normalize.css@*":
version "12.0.0" version "12.0.0"
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4" resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4"
rc-select "~14.1.13" integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==
rc-steps "~6.0.0-alpha.2"
rc-table "~7.26.0" "@csstools/postcss-cascade-layers@^1.0.5":
rc-tabs "~12.4.2"
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.5.tgz#f16f2c4396ace855541e1aa693f5f27ec972e6ad" resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.5.tgz#f16f2c4396ace855541e1aa693f5f27ec972e6ad"
rc-tree-select "~5.5.4"
integrity sha512-Id/9wBT7FkgFzdEpiEWrsVd4ltDxN0rI0QS0SChbeQiSuux3z21SJCRLu6h2cvCEUmaRi+VD0mHFj+GJD4GFnw== integrity sha512-Id/9wBT7FkgFzdEpiEWrsVd4ltDxN0rI0QS0SChbeQiSuux3z21SJCRLu6h2cvCEUmaRi+VD0mHFj+GJD4GFnw==
rc-util "^5.25.2" dependencies:
shallowequal "^1.1.0" "@csstools/selector-specificity" "^2.0.2"
postcss-selector-parser "^6.0.10" postcss-selector-parser "^6.0.10"
@ -8788,6 +8795,11 @@ q@^1.1.2:
csstype@^3.0.10: csstype@^3.0.10:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
csstype@^3.0.2: csstype@^3.0.2:
@ -8858,15 +8870,15 @@ rc-align@^4.0.0:
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
rc-cascader@~3.7.0: integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
version "3.7.0"
resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.7.0.tgz#98134df578ce1cca22be8fb4319b04df4f3dca36" dependencies:
integrity sha512-SFtGpwmYN7RaWEAGTS4Rkc62ZV/qmQGg/tajr/7mfIkleuu8ro9Hlk6J+aA0x1YS4zlaZBtTcSaXM01QMiEV/A==
ms "2.1.2" ms "2.1.2"
rc-select "~14.1.0" debug@^3.2.6, debug@^3.2.7:
version "3.2.7" version "3.2.7"
@ -8900,10 +8912,10 @@ rc-dialog@~9.0.0, rc-dialog@~9.0.2:
version "1.2.0" version "1.2.0"
rc-drawer@~6.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
version "6.0.1"
resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-6.0.1.tgz#437040ac7ba305b5d964ba51e88f30797671e8f8" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
integrity sha512-ibWXGf8I+KRPXE03X4s0/xXzQI37YWXUV+oPy+R29GKxkjr98UTMgwvoQDKlZTm5AiaRuVFqhTKm0kNHqJh+TQ==
decimal.js@^10.2.1: decimal.js@^10.2.1:
@ -8930,15 +8942,16 @@ rc-field-form@~1.27.0:
version "0.7.0" version "0.7.0"
rc-image@~5.12.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
version "5.12.1"
resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.12.1.tgz#1560eda00ef9d33ebdb3c8c74ab134eb00f973d4" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
integrity sha512-FMldR/ODwQmlFlhjR4c6hsOHmnn4s9CxmW7PR/9XCYE1XHlGJ5OkSWOtJruoaLjVwt2tQYDRnLANf/mKZ9ReUg==
deep-is@^0.1.3, deep-is@~0.1.3: deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
@ -9003,10 +9016,10 @@ rc-motion@^2.6.0, rc-motion@^2.6.1, rc-motion@^2.6.2:
integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==
rc-notification@~5.0.0-alpha.9:
version "5.0.0-alpha.9"
resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-5.0.0-alpha.9.tgz#e6fbf5cc786e508f022691a61a03c0473f5ca7b0" delayed-stream@~1.0.0:
integrity sha512-QPvq8VHe2M0SE5DHJf7ADWlvfWKnTsj5FVxcu39gdjX98kKmi+BHY1eTPAQkkdGqd6ZXv6xXHl8qKHyWhQcFPA==
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@ -9090,10 +9103,10 @@ rc-segmented@~2.1.0:
integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==
dependencies: dependencies:
rc-select@~14.1.0:
version "14.1.8" acorn-node "^1.8.2"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.1.8.tgz#986dc9194212ea56c1acd8317a956c7c88e94f4a"
integrity sha512-1kU/7ZCggyR5r5jVEQfAiN6Sq3LGLD2b6FNz5GWel3TOEQZYyDn0o4FAoIRqS6Y5ldWmkFxtd834ilPnG6NV6w== defined "^1.0.0"
minimist "^1.2.6" minimist "^1.2.6"
@ -9101,20 +9114,7 @@ rc-select@~14.1.0:
diacritics@1.3.0: diacritics@1.3.0:
rc-virtual-list "^3.2.0" version "1.3.0"
rc-select@~14.1.13:
version "14.1.16"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.1.16.tgz#0cc4b5a1fc551a2db7c96bc1ece0896317ecdd47"
integrity sha512-71XLHleuZmufpdV2vis5oituRkhg2WNvLpVMJBGWRar6WGAVOHXaY9DR5HvwWry3EGTn19BqnL6Xbybje6f8YA==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-motion "^2.0.1"
rc-overflow "^1.0.0"
rc-trigger "^5.0.4"
rc-util "^5.16.1"
rc-virtual-list "^3.2.0"
resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1" resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1"
@ -9127,10 +9127,10 @@ rc-slider@~10.0.0:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
rc-steps@~6.0.0-alpha.2:
version "6.0.0-alpha.2" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-6.0.0-alpha.2.tgz#505e64177111becd911cef4d24f15477438c9e59"
integrity sha512-d/GPx7ATlPbtFeOVt5FB19W11OBCmRd7lLknt4aSoCI6ukwJqpEhWu2INN4pDOQqN04y3PDsWl1q9hnw+ZC5AA==
diff-sequences@^24.9.0: diff-sequences@^24.9.0:
@ -9145,10 +9145,10 @@ rc-switch@~4.0.0:
diff-sequences@^27.5.1: diff-sequences@^27.5.1:
version "27.5.1" version "27.5.1"
rc-table@~7.26.0:
version "7.26.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.26.0.tgz#9d517e7fa512e7571fdcc453eb1bf19edfac6fbc"
integrity sha512-0cD8e6S+DTGAt5nBZQIPFYEaIukn17sfa5uFL98faHlH/whZzD8ii3dbFL4wmUDEL4BLybhYop+QUfZJ4CPvNQ== integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
@ -9156,10 +9156,10 @@ rc-table@~7.26.0:
version "4.0.2" version "4.0.2"
rc-tabs@~12.4.2: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
version "12.4.2"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.4.2.tgz#487a1b3f8d8cf0bfc121224013dab00d4a8e0532" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
integrity sha512-FFlGwuTjQUznWzJtyhmHc6KAp5lRQFxKUv9Aj1UtsOYe2e7WGmuzcrd+/LQchuPe0VjhaZPdGkmFGcqGqNO6ow==
dir-glob@^3.0.1: dir-glob@^3.0.1:
@ -9197,14 +9197,14 @@ rc-tooltip@~5.2.0:
dns-packet@^5.2.2: dns-packet@^5.2.2:
rc-tree-select@~5.5.4:
version "5.5.5" version "5.4.0"
resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.5.5.tgz#d28b3b45da1e820cd21762ba0ee93c19429bb369"
integrity sha512-k2av7jF6tW9bIO4mQhaVdV4kJ1c54oxV3/hHVU+oD251Gb5JN+m1RbJFTMf1o0rAFqkvto33rxMdpafaGKQRJw== resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b"
integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==
rc-select "~14.1.0" dependencies:
"@leichtgewicht/ip-codec" "^2.0.1" "@leichtgewicht/ip-codec" "^2.0.1"
@ -9259,7 +9259,7 @@ rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.12.0, rc-util@^5.15.0, rc-util@^5.16.
dom-align@^1.7.0: dom-align@^1.7.0:
rc-util@^5.16.0, rc-util@^5.24.2, rc-util@^5.24.4:
version "1.12.3" version "1.12.3"
resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.3.tgz#a36d02531dae0eefa2abb0c4db6595250526f103" resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.3.tgz#a36d02531dae0eefa2abb0c4db6595250526f103"
@ -9277,16 +9277,25 @@ rc-util@^5.21.2, rc-util@^5.23.0:
integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
dependencies: dependencies:
rc-util@^5.25.2:
version "5.25.2" utila "~0.4"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.25.2.tgz#09fd3ce88da7d2149427d51e40a84e3527f5a263"
integrity sha512-OyCO675K/rh4zG3e+LYaHw54WQFEYGV9ibkGawQxqCvf0G0PzUrLQjgZ6SfoHORdbEKN7eQMFn3hHQyA/P8Y5Q==
dom-serializer@0: dom-serializer@0:
shallowequal "^1.1.0"
version "0.2.2" version "0.2.2"
rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.8:
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
dependencies:
domelementtype "^2.0.1"
entities "^2.0.0"
dom-serializer@^1.0.1: dom-serializer@^1.0.1:
@ -10738,6 +10747,11 @@ throat@^6.0.1:
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
dependencies: dependencies:
pend "~1.2.0"
file-entry-cache@^6.0.1: file-entry-cache@^6.0.1:
version "6.0.1" version "6.0.1"