Compare commits

..

6 Commits

Author SHA1 Message Date
Ngọc Long
1d8b0a264e feat(login): add code login limit (#1442) 2023-01-06 18:51:43 +08:00
Yaodong Yu
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
imp2002
4ab2ca7a25 feat: fix checkPermissionForUpdateUser() logic (#1454)
* fix: fix `checkPermissionForUpdateUser()` logic

* fix: fix `checkPermissionForUpdateUser()` logic
2023-01-06 00:03:40 +08:00
June
dcf148fb7f fix: add GetMaskedRoles and GetMaskedPermissions when GetAccount (#1456) 2023-01-06 00:02:52 +08:00
Mr Forest
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
June
0559298d6c feat: extend user with roles and permissions in GetAccount (#1449) 2023-01-04 20:23:57 +08:00
53 changed files with 12446 additions and 11730 deletions

View File

@@ -242,6 +242,7 @@ func (c *ApiController) Signup() {
// @router /logout [get,post]
func (c *ApiController) Logout() {
user := c.GetSessionUsername()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
@@ -271,6 +272,11 @@ func (c *ApiController) GetAccount() {
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))
resp := Response{
Status: "ok",

View File

@@ -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
}
@@ -222,12 +226,13 @@ func (c *ApiController) Login() {
}
// check result through Email or Phone
var checkDest string
if strings.Contains(form.Username, "@") {
verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email
}
checkResult = object.CheckVerificationCode(form.Username, form.Code, c.GetAcceptLanguage())
checkDest = form.Username
} else {
verificationCodeType = "phone"
if len(form.PhonePrefix) == 0 {
@@ -238,11 +243,16 @@ func (c *ApiController) Login() {
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code, c.GetAcceptLanguage())
checkDest = 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
}
checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 {
responseText := fmt.Sprintf("%s%s", verificationCodeType, checkResult)
responseText := fmt.Sprintf("%s - %s", verificationCodeType, checkResult)
c.ResponseError(responseText)
return
}
@@ -253,12 +263,6 @@ func (c *ApiController) Login() {
} else {
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 {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {

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

@@ -17,7 +17,6 @@ package controllers
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/beego/beego/utils/pagination"
@@ -200,18 +199,28 @@ func checkPermissionForUpdateUser(id string, newUser object.User, c *ApiControll
item := object.GetAccountItemByName("Signup application", org)
itemsChanged = append(itemsChanged, item)
}
if reflect.DeepEqual(oldUser.Roles, newUser.Roles) {
oldUserRolesJson, _ := json.Marshal(oldUser.Roles)
newUserRolesJson, _ := json.Marshal(newUser.Roles)
if string(oldUserRolesJson) != string(newUserRolesJson) {
item := object.GetAccountItemByName("Roles", org)
itemsChanged = append(itemsChanged, item)
}
if reflect.DeepEqual(oldUser.Permissions, newUser.Permissions) {
oldUserPermissionJson, _ := json.Marshal(oldUser.Permissions)
newUserPermissionJson, _ := json.Marshal(newUser.Permissions)
if string(oldUserPermissionJson) != string(newUserPermissionJson) {
item := object.GetAccountItemByName("Permissions", org)
itemsChanged = append(itemsChanged, item)
}
if reflect.DeepEqual(oldUser.Properties, newUser.Properties) {
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)
@@ -273,7 +282,7 @@ func (c *ApiController) UpdateUser() {
isGlobalAdmin := c.IsGlobalAdmin()
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
c.ResponseError(c.T(err))
c.ResponseError(err)
return
}

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"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",
"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"
},
"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",
"password is incorrect, you have %d remaining chances": "password is incorrect, you have %d remaining chances"
"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 or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances"
},
"enforcer": {
"Please sign in first": "Please sign in first"

View File

@@ -67,12 +67,12 @@
"Username is too long (maximum is 39 characters).": "用户名过长最大长度为39个字符",
"Username must have at least 2 characters": "用户名至少要有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"
},
"check_util": {
"You have entered the wrong password too many times, please wait for %d minutes and try again": "输入密码错误次数已达上限,请在 %d 分后重试",
"password is incorrect, you have %d remaining chances": "密码错误,您还有 %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 or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会"
},
"enforcer": {
"Please sign in first": "请先登录"
@@ -90,7 +90,7 @@
"You can't unlink yourself, you are not a member of any application": "您无法取消链接,您不是任何应用程序的成员"
},
"organization": {
"Only admin can modify the %s.": "您无法取消链接,您不是任何应用程序的成员",
"Only admin can modify the %s.": "仅允许管理员可以修改 %s",
"The %s is immutable.": "%s是不可变的",
"Unknown modify rule %s.": "未知的修改规则"
},

View File

@@ -222,6 +222,11 @@ func (a *Adapter) createTable() {
if err != nil {
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 {

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
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

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)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
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
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

@@ -269,3 +269,12 @@ func ContainsAsterisk(userId string, users []string) bool {
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()
}
func GetMaskedRoles(roles []*Role) []*Role {
for _, role := range roles {
role.Users = nil
}
return roles
}

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 {
// Forced offline the user first
DeleteSession(user.GetId())
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{})
if err != nil {
panic(err)

View File

@@ -26,6 +26,10 @@ import (
"xorm.io/core"
)
const (
wrongCode = "wrongCode"
)
type VerificationRecord struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@@ -167,7 +171,7 @@ func CheckVerificationCode(dest, code, lang string) string {
}
if record.Code != code {
return "Wrong code!"
return wrongCode
}
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
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/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-webhook", &controllers.ApiController{}, "GET:GetWebhook")
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/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"antd": "5.0.5",
"antd": "5.1.2",
"codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0",

View File

@@ -260,6 +260,13 @@ class AdapterListPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};

View File

@@ -70,6 +70,7 @@ import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next";
import SelectThemeBox from "./SelectThemeBox";
import SessionListPage from "./SessionListPage";
const {Header, Footer, Content} = Layout;
@@ -421,6 +422,10 @@ class App extends Component {
"/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>,
"/webhooks"
));
@@ -517,6 +522,7 @@ class App extends Component {
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />

View File

@@ -13,7 +13,7 @@
// limitations under the License.
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 * as ApplicationBackend from "./backend/ApplicationBackend";
import * as CertBackend from "./backend/CertBackend";
@@ -103,6 +103,7 @@ class ApplicationEditPage extends React.Component {
uploading: false,
mode: props.location.mode !== undefined ? props.location.mode : "edit",
samlMetadata: null,
isAuthorized: true,
};
}
@@ -129,9 +130,15 @@ class ApplicationEditPage extends React.Component {
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
if (res?.status === "error") {
this.setState({
isAuthorized: false,
});
} else {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
}
});
}
@@ -838,6 +845,17 @@ class ApplicationEditPage extends React.Component {
}
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 (
<div>
{

View File

@@ -14,7 +14,7 @@
import React from "react";
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 moment from "moment";
import * as Setting from "./Setting";
@@ -36,6 +36,7 @@ class ApplicationListPage extends BaseListPage {
loading: false,
searchText: "",
searchedColumn: "",
isAuthorized: true,
};
}
@@ -258,6 +259,17 @@ class ApplicationListPage extends BaseListPage {
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 (
<div>
<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,
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.
import React from "react";
import {Button, Input, Space} from "antd";
import {Button, Input, Result, Space} from "antd";
import {SearchOutlined} from "@ant-design/icons";
import Highlighter from "react-highlight-words";
import i18next from "i18next";
class BaseListPage extends React.Component {
constructor(props) {
@@ -127,6 +128,17 @@ class BaseListPage extends React.Component {
};
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 (
<div>
{

View File

@@ -227,6 +227,13 @@ class CertListPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};

View File

@@ -200,6 +200,13 @@ class ModelListPage extends BaseListPage {
searchText: params.searchText,
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
status="403"
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>}
/>
);

View File

@@ -278,6 +278,13 @@ class PaymentListPage extends BaseListPage {
searchText: params.searchText,
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,
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,
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 {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from "antd";
import {Button, Popconfirm, Result, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend";
@@ -36,6 +36,7 @@ class ProviderListPage extends BaseListPage {
loading: false,
searchText: "",
searchedColumn: "",
isAuthorized: true,
};
}
newProvider() {
@@ -227,6 +228,17 @@ class ProviderListPage extends BaseListPage {
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 (
<div>
<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,
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,
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.
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 copy from "copy-to-clipboard";
import * as Setting from "./Setting";
@@ -37,6 +37,7 @@ class ResourceListPage extends BaseListPage {
searchedColumn: "",
fileList: [],
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),
};
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 (
<div>
<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,
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,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};

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

@@ -45,9 +45,9 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme;
export const Themes = [{label: "Dark", key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`},
{label: "Compact", key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`},
{label: "Default", key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`},
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 = {

View File

@@ -288,6 +288,13 @@ class SyncerListPage extends BaseListPage {
searchText: params.searchText,
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,
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 {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 moment from "moment";
import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -38,6 +38,7 @@ class UserListPage extends BaseListPage {
loading: false,
searchText: "",
searchedColumn: "",
isAuthorized: true,
};
}
@@ -369,6 +370,17 @@ class UserListPage extends BaseListPage {
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 (
<div>
<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) {
this.getOrganization(users[0].owner);
}
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
} else {
@@ -432,6 +451,13 @@ class UserListPage extends BaseListPage {
if (users.length > 0) {
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,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};

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

@@ -236,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip",
"Save": "Speichern",
"Save & Exit": "Speichern & Beenden",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Anmelde-URL",
"Signin URL - Tooltip": "sign in url",
"Signup URL": "Registrierungs-URL",
@@ -244,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip",
"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, 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 - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added",

View File

@@ -236,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Signin URL",
"Signin URL - Tooltip": "Signin URL - Tooltip",
"Signup URL": "Signup URL",
@@ -244,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.",
"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 - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added",

View File

@@ -236,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip",
"Save": "Enregistrer",
"Save & Exit": "Enregistrer & Quitter",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "URL de connexion",
"Signin URL - Tooltip": "sign in url",
"Signup URL": "URL d'inscription",
@@ -244,7 +246,7 @@
"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 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 - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added",

View File

@@ -236,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip",
"Save": "保存",
"Save & Exit": "保存して終了",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "サインインURL",
"Signin URL - Tooltip": "sign in url",
"Signup URL": "サインアップURL",
@@ -244,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip",
"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.": "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",

View File

@@ -236,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Signin URL",
"Signin URL - Tooltip": "sign in url",
"Signup URL": "Signup URL",
@@ -244,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.",
"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 - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added",

View File

@@ -236,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip",
"Save": "Сохранить",
"Save & Exit": "Сохранить и выйти",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "URL входа",
"Signin URL - Tooltip": "sign in url",
"Signup URL": "URL регистрации",
@@ -244,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip",
"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.": "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",

View File

@@ -236,6 +236,8 @@
"Roles - Tooltip": "角色",
"Save": "保存",
"Save & Exit": "保存 & 退出",
"Session ID": "会话 ID",
"Sessions": "会话",
"Signin URL": "登录URL",
"Signin URL - Tooltip": "用户的登录地址",
"Signup URL": "注册URL",
@@ -244,7 +246,7 @@
"Signup application - Tooltip": "表示用户注册时通过哪个应用注册的",
"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, 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 - Tooltip": "状态",
"Successfully added": "添加成功",

File diff suppressed because it is too large Load Diff