Compare commits

...

32 Commits

Author SHA1 Message Date
8f7a8d7d4f fix: translation without reloading (#1215)
* fix: translation without reloading

* fix: language switch
2022-10-12 19:52:02 +08:00
23f3fe1e3c feat: update code format (#1214)
* feat: doc

* feat: doc

* Update model.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-10-12 11:42:14 +08:00
59ff5e02ab fix: Add support for including underscores for username (#1210)
* fix: Add support for including underscores for username

* Update check.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-10-11 19:39:19 +08:00
8d41508d6b fix: center loading in account page (#1209)
* fix: center loading in account page

* Update UserEditPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-10-11 00:52:08 +08:00
04f70cf012 Improve renderRightDropdown() 2022-10-10 22:53:47 +08:00
83724c73f9 feat: fix pad and mobile views (#1202)
* fix figure width

* fix: pad resolution menu

* feat: drawer style mobile menu

* fix: menu button i18n
2022-10-10 22:37:25 +08:00
33e419e133 Show more items to org admin 2022-10-10 21:58:17 +08:00
b832c304ae Can get owner in getObject() 2022-10-10 20:56:55 +08:00
4c7f6fda37 fix: Add restriction to username when signing up (#1203) 2022-10-10 19:58:02 +08:00
e4a54fe375 fix: disable roles inputbox when model doesn't support RBAC (#1201)
* feat:Support simple ldap server

* fix:fix review problems

* fix:fix review problems

* fix: fix ldapserver crash bug

* Update ldapserver.go

* fix: fix dulpicate go routines

* fix gofumpt problems

* fix: fix UserList error

* feat:disable 'sub role' when model is incorrect

* feat:disable 'sub role' when model is incorrect

* feat:disable 'sub role' when model is incorrect

* delete useless output

* update func name

* Update PermissionEditPage.js

* Update PermissionEditPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-10-10 00:53:55 +08:00
87da3dad76 Remove useless file 2022-10-09 22:18:38 +08:00
44ad88353f Add error to GetDefaultApplication() 2022-10-09 10:39:33 +08:00
a955fb57d6 feat: fix UserList error (#1194)
* feat:Support simple ldap server

* fix:fix review problems

* fix:fix review problems

* fix: fix ldapserver crash bug

* Update ldapserver.go

* fix: fix dulpicate go routines

* fix gofumpt problems

* fix: fix UserList error
2022-10-08 20:00:45 +08:00
d2960ad66b Fix README typo 2022-10-08 16:00:08 +08:00
5243aabf43 docs: Create SECURITY.md (#1192) 2022-10-07 19:02:35 +08:00
d3a2c2a66e Improve org admin permissions 2022-10-07 16:27:21 +08:00
0a9058a585 Improve user list page 2022-10-07 15:43:50 +08:00
225719810b Update link typo in README 2022-10-06 19:37:00 +08:00
c634d4a891 feat: add some css style for the custom Provider button (#1185)
* fix: add some css style for the custom button

* fix: refactor previous code

* fix: add i18 adaptation

* fix: modifiy the saml codition
2022-10-06 19:28:02 +08:00
3dc01ec85d fix: language widget poisition without border css (#1188) 2022-10-06 17:26:12 +08:00
a7324f1da1 Improve className 2022-10-03 22:45:36 +08:00
6da452d7e0 feat: show language widget in signup and signin pages (#1180) 2022-10-03 22:40:19 +08:00
5abcf913e6 Fix language menu 2022-10-03 22:39:10 +08:00
58455e688e Improve WebAuthnCredentialTable and border radius 2022-10-03 18:46:40 +08:00
4d6f68eddc Improve footer and color 2022-10-03 17:43:19 +08:00
67f3c5a489 Add verificationCode to login method 2022-10-03 15:41:20 +08:00
9c48582e0c feat: fix bugs in webauthn (#1173) 2022-10-01 11:10:55 +08:00
645c631db9 fix: fix the delete file vulnerability issue (#1174) 2022-10-01 00:33:27 +08:00
3128e68df4 Add sendSilentSigninData() 2022-09-30 01:51:58 +08:00
2247c6a883 Add isSelf() in user edit page 2022-09-29 22:24:05 +08:00
04709f731b Update beego to v1.12.11 2022-09-29 19:45:17 +08:00
ebe1887e8b feat: add saml provider error (#1168)
* fix: add saml provider error

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

* fix: search

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

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-09-28 21:09:39 +08:00
74 changed files with 466 additions and 278 deletions

0
$env
View File

View File

@ -51,7 +51,7 @@
## Documentation
- International: https://casdoor.org
- Asian mirror: https://docs.casdoor.cn
- Asian mirror: https://casdoor.cn
## Install
@ -69,7 +69,7 @@ https://casdoor.org/docs/how-to-connect/overview
## Integrations
https://casdoor.org/docs/integration/apisix
https://casdoor.org/docs/category/integrations
## How to contact?

9
SECURITY.md Normal file
View File

@ -0,0 +1,9 @@
# Security Policy
## Reporting a Vulnerability
We are grateful for security researchers and users reporting a vulnerability to us first. To ensure that your request is handled in a timely manner and we can keep users safe, please follow the below guidelines.
- **Please do not report security vulnerabilities directly on GitHub.**
- To report a vulnerability, please email [admin@casdoor.org](admin@casdoor.org).

View File

@ -15,12 +15,14 @@
package authz
import (
"fmt"
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v3"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
stringadapter "github.com/qiangmzsx/string-adapter/v2"
)
@ -138,6 +140,12 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
}
}
userId := fmt.Sprintf("%s/%s", subOwner, subName)
user := object.GetUser(userId)
if user != nil && user.IsAdmin && subOwner == objOwner {
return true
}
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
if err != nil {
panic(err)

View File

@ -21,7 +21,7 @@ import (
"strconv"
"strings"
"github.com/astaxie/beego"
"github.com/beego/beego"
)
func init() {

View File

@ -18,7 +18,7 @@ import (
"os"
"testing"
"github.com/astaxie/beego"
"github.com/beego/beego"
"github.com/stretchr/testify/assert"
)

View File

@ -203,6 +203,12 @@ func (c *ApiController) Signup() {
}
}
msg = object.CheckUsername(user.Name)
if msg != "" {
c.ResponseError(msg)
return
}
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -411,6 +411,12 @@ func (c *ApiController) Login() {
// sync info from 3rd-party if possible
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
msg := object.CheckUsername(user.Name)
if msg != "" {
c.ResponseError(msg)
return
}
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))

View File

@ -18,8 +18,8 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/beego/beego"
"github.com/beego/beego/logs"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -133,11 +133,12 @@ func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
application := object.GetMaskedApplication(object.GetDefaultApplication(id), userId)
if application == nil {
c.ResponseError("Please set a default application for this organization")
application, err := object.GetDefaultApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(application)
maskedApplication := object.GetMaskedApplication(application, userId)
c.ResponseOk(maskedApplication)
}

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -22,7 +22,7 @@ import (
"mime"
"path/filepath"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"net/http"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -19,7 +19,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -158,6 +158,12 @@ func (c *ApiController) UpdateUser() {
columns = strings.Split(columnsStr, ",")
}
msg := object.CheckUsername(user.Name)
if msg != "" {
c.ResponseError(msg)
return
}
isGlobalAdmin := c.IsGlobalAdmin()
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
if affected {
@ -183,6 +189,12 @@ func (c *ApiController) AddUser() {
return
}
msg := object.CheckUsername(user.Name)
if msg != "" {
c.ResponseError(msg)
return
}
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
c.ServeJSON()
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"bytes"
"fmt"
"io"
"github.com/casdoor/casdoor/object"
@ -100,7 +101,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
userName := c.Input().Get("name")
user := object.GetUserByFields(userOwner, userName)
if user == nil {
c.ResponseError("Please Giveout Owner and Username.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", userOwner, userName))
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)
@ -121,6 +122,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType")
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
sessionObj := c.GetSession("authentication")
sessionData, ok := sessionObj.(webauthn.SessionData)
@ -138,5 +140,11 @@ func (c *ApiController) WebAuthnSigninFinish() {
}
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
c.ResponseOk(userId)
application := object.GetApplicationByUser(user)
var form RequestForm
form.Type = responseType
resp := c.HandleLoggedIn(application, user, &form)
c.Data["json"] = resp
c.ServeJSON()
}

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

2
go.mod
View File

@ -5,8 +5,8 @@ go 1.16
require (
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/astaxie/beego v1.12.3
github.com/aws/aws-sdk-go v1.44.4
github.com/beego/beego v1.12.11
github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v3 v3.0.1

17
go.sum
View File

@ -74,13 +74,13 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0q
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beego/beego v1.12.11 h1:MWKcnpavb7iAIS0m6uuEq6pHKkYvGNw/5umIUKqL7jM=
github.com/beego/beego v1.12.11/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
@ -115,9 +115,9 @@ github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1C
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbase/go-couchbase v0.0.0-20201216133707-c04035124b17/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
github.com/couchbase/gomemcached v0.1.2-0.20201224031647-c432ccf49f32/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -341,7 +341,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -412,7 +411,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
@ -430,7 +428,7 @@ github.com/twilio/twilio-go v0.26.0/go.mod h1:lz62Hopu4vicpQ056H5TJ0JE4AP0rS3sQ3
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -458,6 +456,7 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@ -566,6 +565,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -597,6 +597,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -18,9 +18,9 @@ import (
"flag"
"fmt"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
_ "github.com/astaxie/beego/session/redis"
"github.com/beego/beego"
"github.com/beego/beego/logs"
_ "github.com/beego/beego/session/redis"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/controllers"

View File

@ -18,7 +18,7 @@ import (
"fmt"
"runtime"
"github.com/astaxie/beego"
"github.com/beego/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
_ "github.com/denisenkom/go-mssqldb" // db = mssql

View File

@ -313,3 +313,19 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
}
return allowed, err
}
func CheckUsername(name string) string {
if name == "" {
return "Empty username."
} else if len(name) > 39 {
return "Username is too long (maximum is 39 characters)."
}
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
if !re.MatchString(name) {
return "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline."
}
return ""
}

View File

@ -19,7 +19,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/beego/beego"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
@ -409,6 +409,7 @@ func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRe
}
}
}
if !found && !AddUser(&User{
Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber),

View File

@ -5,7 +5,7 @@ import (
"sync"
"time"
"github.com/astaxie/beego/logs"
"github.com/beego/beego/logs"
"github.com/casdoor/casdoor/util"
)

View File

@ -218,14 +218,14 @@ func CheckAccountItemModifyRule(accountItem *AccountItem, user *User) (bool, str
return true, ""
}
func GetDefaultApplication(id string) *Application {
func GetDefaultApplication(id string) (*Application, error) {
organization := GetOrganization(id)
if organization == nil {
return nil
return nil, fmt.Errorf("The organization: %s does not exist", id)
}
if organization.DefaultApplication != "" {
return getApplication("admin", organization.DefaultApplication)
return getApplication("admin", organization.DefaultApplication), fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
}
applications := []*Application{}
@ -235,7 +235,7 @@ func GetDefaultApplication(id string) *Application {
}
if len(applications) == 0 {
return nil
return nil, fmt.Errorf("The application does not exist")
}
defaultApplication := applications[0]
@ -249,5 +249,5 @@ func GetDefaultApplication(id string) *Application {
extendApplicationWithProviders(defaultApplication)
extendApplicationWithOrg(defaultApplication)
return defaultApplication
return defaultApplication, nil
}

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
)

View File

@ -127,6 +127,11 @@ func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.B
}
func DeleteFile(provider *Provider, objectKey string) error {
// check fullFilePath is there security issue
if strings.Contains(objectKey, "..") {
return fmt.Errorf("the objectKey: %s is not allowed", objectKey)
}
endpoint := getProviderEndpoint(provider)
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
if storageProvider == nil {

View File

@ -703,7 +703,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
}
// Add new user
var name string
if username != "" {
if CheckUsername(username) == "" {
name = username
} else {
name = fmt.Sprintf("wechat-%s", openId)

View File

@ -159,7 +159,7 @@ func DisableVerificationCode(dest string) {
}
}
// from Casnode/object/validateCode.go line 116
// From Casnode/object/validateCode.go line 116
var stdNums = []byte("0123456789")
func getRandomCode(length int) string {

View File

@ -20,7 +20,7 @@ import (
"net/http"
"strings"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/util"
)
@ -63,11 +63,16 @@ func getObject(ctx *context.Context) (string, string) {
if method == http.MethodGet {
// query == "?id=built-in/admin"
id := ctx.Input.Query("id")
if id == "" {
return "", ""
if id != "" {
return util.GetOwnerAndNameFromId(id)
}
return util.GetOwnerAndNameFromId(id)
owner := ctx.Input.Query("owner")
if owner != "" {
return owner, ""
}
return "", ""
} else {
body := ctx.Input.RequestBody

View File

@ -17,7 +17,7 @@ package routers
import (
"fmt"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -17,7 +17,7 @@ package routers
import (
"net/http"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
)

View File

@ -17,7 +17,7 @@ package routers
import (
"fmt"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)

View File

@ -20,7 +20,7 @@
package routers
import (
"github.com/astaxie/beego"
"github.com/beego/beego"
"github.com/casdoor/casdoor/controllers"
)

View File

@ -19,7 +19,7 @@ import (
"os"
"strings"
"github.com/astaxie/beego/context"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
)

View File

@ -19,8 +19,8 @@ import (
"net/http"
"strings"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/beego/beego/context"
"github.com/beego/beego/logs"
)
func GetIPInfo(clientIP string) string {

View File

@ -17,7 +17,7 @@ package util
import (
"fmt"
"github.com/astaxie/beego/logs"
"github.com/beego/beego/logs"
)
func SafeGoroutine(fn func()) {

View File

@ -43,7 +43,7 @@ module.exports = {
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {"@primary-color": "rgb(45,120,213)"},
modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"},
javascriptEnabled: true,
},
},

View File

@ -16,8 +16,8 @@ import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Button, Card, Dropdown, Layout, Menu, Result} from "antd";
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Button, Card, Drawer, Dropdown, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
@ -74,6 +74,7 @@ import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next";
const {Header, Footer} = Layout;
@ -85,6 +86,7 @@ class App extends Component {
selectedMenuKey: 0,
account: undefined,
uri: null,
menuVisible: false,
};
Setting.initServerUrl();
@ -298,12 +300,12 @@ class App extends Component {
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="/account">
<SettingOutlined />
&nbsp;
&nbsp;&nbsp;
{i18next.t("account:My Account")}
</Menu.Item>
<Menu.Item key="/logout">
<LogoutOutlined />
&nbsp;
&nbsp;&nbsp;
{i18next.t("account:Logout")}
</Menu.Item>
</Menu>
@ -378,6 +380,9 @@ class App extends Component {
</Link>
</Menu.Item>
);
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/users">
<Link to="/users">
@ -592,6 +597,18 @@ class App extends Component {
);
}
onClose = () => {
this.setState({
menuVisible: false,
});
};
showMenu = () => {
this.setState({
menuVisible: true,
});
};
renderContent() {
if (!Setting.isMobile()) {
return (
@ -610,7 +627,7 @@ class App extends Component {
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px", width: "80%", position: "absolute", left: "145px"}}
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}}
>
{
this.renderMenu()
@ -643,22 +660,28 @@ class App extends Component {
</Link>
)
}
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
mode={(Setting.isMobile()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
onClick={this.onClose}
>
{
this.renderMenu()
}
</Menu>
</Drawer>
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")}
</Button>
<div style = {{float: "right"}}>
{
this.renderAccount()
}
<SelectLanguageBox />
</div>
</Menu>
</Header>
{
this.renderRouter()
@ -682,7 +705,7 @@ class App extends Component {
textAlign: "center",
}
}>
Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
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>
</Footer>
</>
);
@ -776,4 +799,4 @@ class App extends Component {
}
}
export default withRouter(App);
export default withRouter(withTranslation()(App));

View File

@ -28,7 +28,7 @@
color: #61dafb;
}
#root{
#root {
height: 100%;
}
@ -59,11 +59,18 @@
height: 70px; /* Footer height */
}
.language_box {
#language-box-corner {
position: absolute;
top: 75px;
right: 0;
}
.language-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px;
background-position: center;
background-repeat: no-repeat;
border-radius: 5px;
width: 45px;
height: 65px;
float: right;
@ -87,9 +94,19 @@
align-items: stretch;
}
.login-content {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
margin: 0 auto;
position: relative;
}
.loginBackground {
height: 100%;
background: #ffffff no-repeat;
background: #fff no-repeat;
background-size: 100% 100%;
background-attachment: fixed;
}

View File

@ -35,6 +35,7 @@ class PermissionEditPage extends React.Component {
permissionName: props.match.params.permissionName,
permission: null,
organizations: [],
model: null,
users: [],
roles: [],
models: [],
@ -59,6 +60,7 @@ class PermissionEditPage extends React.Component {
this.getRoles(permission.owner);
this.getModels(permission.owner);
this.getResources(permission.owner);
this.getModel(permission.owner, permission.model);
});
}
@ -98,6 +100,15 @@ class PermissionEditPage extends React.Component {
});
}
getModel(organizationName, modelName) {
ModelBackend.getModel(organizationName, modelName)
.then((res) => {
this.setState({
model: res,
});
});
}
getResources(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
@ -115,6 +126,10 @@ class PermissionEditPage extends React.Component {
}
updatePermissionField(key, value) {
if (key === "model") {
this.getModel(this.state.permission.owner, value);
}
value = this.parsePermissionField(key, value);
const permission = this.state.permission;
@ -124,6 +139,13 @@ class PermissionEditPage extends React.Component {
});
}
hasRoleDefinition(model) {
if (model !== null) {
return model.modelText.includes("role_definition");
}
return false;
}
renderPermission() {
return (
<Card size="small" title={
@ -214,7 +236,7 @@ class PermissionEditPage extends React.Component {
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField("roles", value);})}>
<Select virtual={false} disabled={!this.hasRoleDefinition(this.state.model)} mode="tags" style={{width: "100%"}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField("roles", value);})}>
{
this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission, index) => <Option key={index} value={`${permission.owner}/${permission.name}`}>{`${permission.owner}/${permission.name}`}</Option>)
}

View File

@ -341,7 +341,7 @@ class PermissionListPage extends BaseListPage {
this.setState({loading: true});
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
getPermissions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -25,7 +25,7 @@ class RoleListPage extends BaseListPage {
newRole() {
const randomName = Setting.getRandomName();
return {
owner: "built-in",
owner: this.props.account.owner,
name: `role_${randomName}`,
createdTime: moment().format(),
displayName: `New Role - ${randomName}`,
@ -211,7 +211,7 @@ class RoleListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
RoleBackend.getRoles("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
RoleBackend.getRoles(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -37,8 +37,8 @@ class SelectLanguageBox extends React.Component {
Setting.changeLanguage(e.key);
}}>
<Menu.Item key="en" icon={flagIcon("US", "English")}>English</Menu.Item>
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
<Menu.Item key="zh" icon={flagIcon("CN", "简体中文")}>简体中文</Menu.Item>
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
<Menu.Item key="fr" icon={flagIcon("FR", "Français")}>Français</Menu.Item>
<Menu.Item key="de" icon={flagIcon("DE", "Deutsch")}>Deutsch</Menu.Item>
<Menu.Item key="ja" icon={flagIcon("JP", "日本語")}>日本語</Menu.Item>
@ -49,7 +49,7 @@ class SelectLanguageBox extends React.Component {
return (
<Dropdown overlay={menu} >
<div className="language_box" />
<div className="language-box" id={this.props.id} style={this.props.style} />
</Dropdown>
);
}

View File

@ -554,7 +554,7 @@ export function changeLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
window.location.reload(true);
// window.location.reload(true);
}
export function changeMomentLanguage(language) {

View File

@ -127,8 +127,12 @@ class UserEditPage extends React.Component {
this.getUser();
}
isSelf() {
return (this.state.user.id === this.props.account?.id);
}
isSelfOrAdmin() {
return (this.state.user.id === this.props.account?.id) || Setting.isAdminUser(this.props.account);
return this.isSelf() || Setting.isAdminUser(this.props.account);
}
renderAccountItem(accountItem) {
@ -136,7 +140,6 @@ class UserEditPage extends React.Component {
return null;
}
const isSelf = this.state.user.id === this.props.account?.id;
const isAdmin = Setting.isAdminUser(this.props.account);
// return (
@ -148,7 +151,7 @@ class UserEditPage extends React.Component {
// )
if (accountItem.viewRule === "Self") {
if (!isSelf && !isAdmin) {
if (!this.isSelfOrAdmin()) {
return null;
}
} else if (accountItem.viewRule === "Admin") {
@ -159,7 +162,7 @@ class UserEditPage extends React.Component {
let disabled = false;
if (accountItem.modifyRule === "Self") {
if (!isSelf && !isAdmin) {
if (!this.isSelfOrAdmin()) {
disabled = true;
}
} else if (accountItem.modifyRule === "Admin") {
@ -296,7 +299,11 @@ class UserEditPage extends React.Component {
}} />
</Col>
<Col span={11} >
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
{
!this.isSelf() ? null : (
<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />
)
}
</Col>
</Row>
);
@ -556,7 +563,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:WebAuthn credentials"), i18next.t("user:WebAuthn credentials"))} :
</Col>
<Col span={22} >
<WebAuthnCredentialTable table={this.state.user.webauthnCredentials} updateTable={(table) => {this.updateUserField("webauthnCredentials", table);}} refresh={this.getUser.bind(this)} />
<WebAuthnCredentialTable isSelf={this.isSelf()} table={this.state.user.webauthnCredentials} updateTable={(table) => {this.updateUserField("webauthnCredentials", table);}} refresh={this.getUser.bind(this)} />
</Col>
</Row>
);
@ -654,7 +661,7 @@ class UserEditPage extends React.Component {
return (
<div>
{
this.state.loading ? <Spin size="large" /> : (
this.state.loading ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
this.state.user !== null ? this.renderUser() :
<Result
status="404"

View File

@ -374,7 +374,7 @@ class UserListPage extends BaseListPage {
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
if (this.state.organizationName === undefined) {
UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -19,40 +19,6 @@ import * as UserWebauthnBackend from "./backend/UserWebauthnBackend";
import * as Setting from "./Setting";
class WebAuthnCredentialTable extends React.Component {
render() {
const columns = [
{
title: i18next.t("user:WebAuthn credentials"),
dataIndex: "ID",
key: "ID",
},
{
title: i18next.t("general:Action"),
key: "action",
render: (text, record, index) => {
return (
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
{i18next.t("general:Delete")}
</Button>
);
},
},
];
return (
<Table scroll={{x: "max-content"}} rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
title={() => (
<div>
{i18next.t("user:WebAuthn credentials")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
{i18next.t("general:Add")}
</Button>
</div>
)}
/>
);
}
deleteRow(table, i) {
table = Setting.deleteRow(table, i);
this.props.updateTable(table);
@ -71,6 +37,41 @@ class WebAuthnCredentialTable extends React.Component {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
});
}
render() {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "ID",
key: "ID",
},
{
title: i18next.t("general:Action"),
key: "action",
width: "170px",
render: (text, record, index) => {
return (
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
{i18next.t("general:Delete")}
</Button>
);
},
},
];
return (
<Table rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
title={() => (
<div>
{i18next.t("user:WebAuthn credentials")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button disabled={!this.props.isSelf} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
{i18next.t("general:Add")}
</Button>
</div>
)}
/>
);
}
}
export default WebAuthnCredentialTable;

View File

@ -37,7 +37,7 @@ export function getEmailAndPhone(values) {
}).then((res) => res.json());
}
function oAuthParamsToQuery(oAuthParams) {
export function oAuthParamsToQuery(oAuthParams) {
// login
if (oAuthParams === null) {
return "";

View File

@ -28,6 +28,8 @@ import SelfLoginButton from "./SelfLoginButton";
import i18next from "i18next";
import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next";
const {TabPane} = Tabs;
@ -41,7 +43,6 @@ class LoginPage extends React.Component {
owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
application: null,
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
isCodeSignin: false,
msg: null,
username: null,
validEmailOrPhone: false,
@ -269,23 +270,6 @@ class LoginPage extends React.Component {
}
}
getSamlUrl(provider) {
const params = new URLSearchParams(this.props.location.search);
const clientId = params.get("client_id");
const application = params.get("state");
const realRedirectUri = params.get("redirect_uri");
const redirectUri = `${window.location.origin}/callback/saml`;
const providerName = provider.name;
const relayState = `${clientId}&${application}&${providerName}&${realRedirectUri}&${redirectUri}`;
AuthBackend.getSamlLogin(`${provider.owner}/${providerName}`, btoa(relayState)).then((res) => {
if (res.data2 === "POST") {
document.write(res.data);
} else {
window.location.href = res.data;
}
});
}
isProviderVisible(providerItem) {
if (this.state.mode === "signup") {
return Setting.isProviderVisibleForSignUp(providerItem);
@ -366,7 +350,7 @@ class LoginPage extends React.Component {
},
{
validator: (_, value) => {
if (this.state.isCodeSignin) {
if (this.state.loginMethod === "verificationCode") {
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
@ -389,7 +373,7 @@ class LoginPage extends React.Component {
<Input
id = "input"
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder={this.state.isCodeSignin ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
disabled={!application.enablePassword}
onChange={e => {
this.setState({
@ -416,29 +400,17 @@ class LoginPage extends React.Component {
</a>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{
this.state.loginMethod === "password" ?
(
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{i18next.t("login:Sign In")}
</Button>
) :
(
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{i18next.t("login:Sign in with WebAuthn")}
</Button>
)
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
i18next.t("login:Sign In")
}
</Button>
{
this.renderFooter(application)
}
@ -446,7 +418,7 @@ class LoginPage extends React.Component {
<Form.Item>
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small");
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small", this.props.location);
})
}
</Form.Item>
@ -467,7 +439,7 @@ class LoginPage extends React.Component {
<br />
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big");
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
})
}
<div>
@ -496,19 +468,6 @@ class LoginPage extends React.Component {
} else {
return (
<React.Fragment>
<span style={{float: "left"}}>
{
!application.enableCodeSignin ? null : (
<a onClick={() => {
this.setState({
isCodeSignin: !this.state.isCodeSignin,
});
}}>
{this.state.isCodeSignin ? i18next.t("login:Sign in with password") : i18next.t("login:Sign in with code")}
</a>
)
}
</span>
<span style={{float: "right"}}>
{
!application.enableSignUp ? null : (
@ -529,14 +488,19 @@ class LoginPage extends React.Component {
}
}
renderSignedInBox() {
if (this.props.account === undefined || this.props.account === null) {
if (window !== window.parent) {
const message = {tag: "Casdoor", type: "SilentSignin", data: "user-not-logged-in"};
sendSilentSigninData(data) {
if (Setting.inIframe()) {
const message = {tag: "Casdoor", type: "SilentSignin", data: data};
window.parent.postMessage(message, "*");
}
}
renderSignedInBox() {
if (this.props.account === undefined || this.props.account === null) {
this.sendSilentSigninData("user-not-logged-in");
return null;
}
const application = this.getApplicationObj();
if (this.props.account.owner !== application.organization) {
return null;
@ -545,10 +509,7 @@ class LoginPage extends React.Component {
const params = new URLSearchParams(this.props.location.search);
const silentSignin = params.get("silentSignin");
if (silentSignin !== null) {
if (window !== window.parent) {
const message = {tag: "Casdoor", type: "SilentSignin", data: "signing-in"};
window.parent.postMessage(message, "*");
}
this.sendSilentSigninData("signing-in");
const values = {};
values["application"] = this.state.application.name;
@ -614,7 +575,7 @@ class LoginPage extends React.Component {
const rawId = assertion.rawId;
const sig = assertion.response.signature;
const userHandle = assertion.response.userHandle;
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/finish`, {
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/finish${AuthBackend.oAuthParamsToQuery(oAuthParams)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify({
@ -654,7 +615,23 @@ class LoginPage extends React.Component {
renderPasswordOrCodeInput() {
const application = this.getApplicationObj();
if (this.state.loginMethod === "password") {
return this.state.isCodeSignin ? (
return (
<Col span={24}>
<Form.Item
name="password"
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
>
<Input.Password
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder={i18next.t("login:Password")}
disabled={!application.enablePassword}
/>
</Form.Item>
</Col>
);
} else if (this.state.loginMethod === "verificationCode") {
return (
<Col span={24}>
<Form.Item
name="code"
@ -667,32 +644,29 @@ class LoginPage extends React.Component {
/>
</Form.Item>
</Col>
) : (
<Col span={24}>
<Form.Item
name="password"
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
>
<Input
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder={i18next.t("login:Password")}
disabled={!application.enablePassword}
/>
</Form.Item>
</Col>
);
} else {
return null;
}
}
renderMethodChoiceBox() {
const application = this.getApplicationObj();
if (application.enableWebAuthn) {
if (application.enableCodeSignin || application.enableWebAuthn) {
return (
<div>
<Tabs defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
<Tabs size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
<TabPane tab={i18next.t("login:Password")} key="password" />
<TabPane tab={"WebAuthn"} key="webAuthn" />
{
!application.enableCodeSignin ? null : (
<TabPane tab={i18next.t("login:Verification Code")} key="verificationCode" />
)
}
{
!application.enableWebAuthn ? null : (
<TabPane tab={i18next.t("login:WebAuthn")} key="webAuthn" />
)
}
</Tabs>
</div>
);
@ -726,9 +700,11 @@ class LoginPage extends React.Component {
return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
<CustomGithubCorner />
<Row >
<Row>
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}}>
<div className="login-content">
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center", ...formStyle}}>
<SelectLanguageBox id="language-box-corner" style={{top: formStyle !== null ? "80px" : "45px", right: formStyle !== null ? "5px" : "-45px"}} />
<div>
{
Setting.renderHelmet(application)
@ -746,7 +722,7 @@ class LoginPage extends React.Component {
this.renderForm(application)
}
</div>
</div>
</div>
</Col>
</Row>
@ -755,4 +731,4 @@ class LoginPage extends React.Component {
}
}
export default LoginPage;
export default withTranslation()(LoginPage);

View File

@ -39,6 +39,7 @@ import SteamLoginButton from "./SteamLoginButton";
import BilibiliLoginButton from "./BilibiliLoginButton";
import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton";
import * as AuthBackend from "./AuthBackend";
function getSigninButton(type) {
const text = i18next.t("login:Sign in with {type}").replace("{type}", type);
@ -95,7 +96,24 @@ function getSigninButton(type) {
return text;
}
export function renderProviderLogo(provider, application, width, margin, size) {
function getSamlUrl(provider, location) {
const params = new URLSearchParams(location.search);
const clientId = params.get("client_id");
const application = params.get("state");
const realRedirectUri = params.get("redirect_uri");
const redirectUri = `${window.location.origin}/callback/saml`;
const providerName = provider.name;
const relayState = `${clientId}&${application}&${providerName}&${realRedirectUri}&${redirectUri}`;
AuthBackend.getSamlLogin(`${provider.owner}/${providerName}`, btoa(relayState)).then((res) => {
if (res.data2 === "POST") {
document.write(res.data);
} else {
window.location.href = res.data;
}
});
}
export function renderProviderLogo(provider, application, width, margin, size, location) {
if (size === "small") {
if (provider.category === "OAuth") {
return (
@ -105,12 +123,38 @@ export function renderProviderLogo(provider, application, width, margin, size) {
);
} else if (provider.category === "SAML") {
return (
<a key={provider.displayName} onClick={this.getSamlUrl.bind(this, provider)}>
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)}>
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
}
} else if (provider.type === "Custom") {
// style definition
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName);
const customAStyle = {display: "block", height: "55px", color: "#000"};
const customButtonStyle = {display: "flex", alignItems: "center", width: "calc(100% - 10px)", height: "50px", margin: "5px", padding: "0 10px", backgroundColor: "transparent", boxShadow: "0px 1px 3px rgba(0,0,0,0.5)", border: "0px", borderRadius: "3px", cursor: "pointer"};
const customImgStyle = {justfyContent: "space-between"};
const customSpanStyle = {textAlign: "center", lineHeight: "50px", width: "100%", fontSize: "19px"};
if (provider.category === "OAuth") {
return (
<a key={provider.displayName} href={Provider.getAuthUrl(application, provider, "signup")} style={customAStyle}>
<button style={customButtonStyle}>
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
<span style={customSpanStyle}>{text}</span>
</button>
</a>
);
} else if (provider.category === "SAML") {
return (
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)} style={customAStyle}>
<button style={customButtonStyle}>
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
<span style={customSpanStyle}>{text}</span>
</button>
</a>
);
}
} else {
return (
<div key={provider.displayName} style={{marginBottom: "10px"}}>

View File

@ -51,7 +51,7 @@ class SamlCallback extends React.Component {
const samlResponse = params.get("samlResponse");
const messages = atob(relayState).split("&");
const clientId = messages[0];
const applicationName = messages[1] === "null" ? "app-built-in" : messages[1];
const applicationName = (messages[1] === "null" || messages[1] === "undefined") ? "app-built-in" : messages[1];
const providerName = messages[2];
const redirectUri = messages[3];
const responseType = this.getResponseType(redirectUri);

View File

@ -25,6 +25,7 @@ import * as ApplicationBackend from "../backend/ApplicationBackend";
import {CountDownInput} from "../common/CountDownInput";
import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner";
import SelectLanguageBox from "../SelectLanguageBox";
const formItemLayout = {
labelCol: {
@ -595,7 +596,7 @@ class SignupPage extends React.Component {
</Form.Item>
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small");
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small", this.props.location);
})
}
</Form>
@ -622,7 +623,9 @@ class SignupPage extends React.Component {
&nbsp;
<Row>
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}} >
<div className="login-content">
<div style={{marginBottom: "10px", textAlign: "center", ...formStyle}}>
<SelectLanguageBox id="language-box-corner" style={{top: formStyle !== null ? "3px" : "-20px", right: formStyle !== null ? "5px" : "-45px"}} />
{
Setting.renderHelmet(application)
}
@ -633,6 +636,7 @@ class SignupPage extends React.Component {
this.renderForm(application)
}
</div>
</div>
</Col>
</Row>
{

View File

@ -44,10 +44,11 @@ class SingleCard extends React.Component {
return (
<Card.Grid style={gridStyle} onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}>
<img src={logo} alt="logo" height={60} style={{marginBottom: "20px"}} />
<img src={logo} alt="logo" width={"100%"} style={{marginBottom: "20px"}} />
<Meta
title={title}
description={desc}
style={{justifyContent: "center"}}
/>
</Card.Grid>
);
@ -61,7 +62,7 @@ class SingleCard extends React.Component {
<Card
hoverable
cover={
<img alt="logo" src={logo} style={{width: "100%", height: "210px", objectFit: "scale-down"}} />
<img alt="logo" src={logo} style={{width: "100%", objectFit: "scale-down"}} />
}
onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}
style={isSingle ? {width: "320px"} : {width: "100%"}}

View File

@ -87,7 +87,7 @@ export const CaptchaPreview = ({
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "3px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: 10,
}}

View File

@ -101,7 +101,7 @@ export const CountDownInput = (props) => {
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "3px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: 10,
}}

View File

@ -23,6 +23,7 @@ import ja from "./locales/ja/data.json";
import es from "./locales/es/data.json";
import * as Conf from "./Conf";
import * as Setting from "./Setting";
import {initReactI18next} from "react-i18next";
const resources = {
en: en,
@ -80,7 +81,7 @@ function initLanguage() {
return language;
}
i18n.init({
i18n.use(initReactI18next).init({
lng: initLanguage(),
resources: resources,

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client-IP",
"Close": "Close",
"Created time": "Erstellte Zeit",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Master-Passwort",
"Master password - Tooltip": "Masterpasswort - Tooltip",
"Menu": "Menu",
"Method": "Methode",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
"Sign In": "Anmelden",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Mit Code anmelden",
"Sign in with password": "Mit Passwort anmelden",
"Sign in with {type}": "Mit {type} anmelden",
"Signing in...": "Anmelden...",
"The input is not valid Email or Phone!": "Die Eingabe ist keine gültige E-Mail oder Telefon!",
"To access": "Zu Zugriff",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "jetzt anmelden",
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
},

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client IP",
"Close": "Close",
"Created time": "Created time",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "Logo - Tooltip",
"Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip",
"Menu": "Menu",
"Method": "Method",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Sign in with code",
"Sign in with password": "Sign in with password",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
"To access": "To access",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "sign up now",
"username, Email or phone": "username, Email or phone"
},

View File

@ -133,6 +133,7 @@
"Certs": "Certes",
"Click to Upload": "Click to Upload",
"Client IP": "IP du client",
"Close": "Close",
"Created time": "Date de création",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Mot de passe maître",
"Master password - Tooltip": "Mot de passe maître - Infobulle",
"Menu": "Menu",
"Method": "Méthode",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
"Sign In": "Se connecter",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Se connecter avec le code",
"Sign in with password": "Se connecter avec le mot de passe",
"Sign in with {type}": "Se connecter avec {type}",
"Signing in...": "Connexion en cours...",
"The input is not valid Email or Phone!": "L'entrée n'est pas valide Email ou Téléphone !",
"To access": "Pour accéder à",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "inscrivez-vous maintenant",
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
},

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "クライアント IP",
"Close": "Close",
"Created time": "作成日時",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "マスターパスワード",
"Master password - Tooltip": "マスターパスワード - ツールチップ",
"Menu": "Menu",
"Method": "方法",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
"Sign In": "サインイン",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "コードでサインイン",
"Sign in with password": "パスワードでサインイン",
"Sign in with {type}": "{type} でサインイン",
"Signing in...": "サインイン中...",
"The input is not valid Email or Phone!": "入力されたメールアドレスまたは電話番号が正しくありません。",
"To access": "アクセスするには",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "今すぐサインアップ",
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
},

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client IP",
"Close": "Close",
"Created time": "Created time",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip",
"Menu": "Menu",
"Method": "Method",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Sign in with code",
"Sign in with password": "Sign in with password",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
"To access": "To access",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "sign up now",
"username, Email or phone": "username, Email or phone"
},

View File

@ -133,6 +133,7 @@
"Certs": "Сертификаты",
"Click to Upload": "Нажмите здесь, чтобы загрузить",
"Client IP": "IP клиента",
"Close": "Close",
"Created time": "Время создания",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Мастер-пароль",
"Master password - Tooltip": "Мастер-пароль - Tooltip",
"Menu": "Menu",
"Method": "Метод",
"Model": "Модель",
"Model - Tooltip": "Модель - Подсказка",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
"Sign In": "Войти",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Войти с помощью кода",
"Sign in with password": "Войти с помощью пароля",
"Sign in with {type}": "Войти с помощью {type}",
"Signing in...": "Вход...",
"The input is not valid Email or Phone!": "Введен неверный адрес электронной почты или телефон!",
"To access": "На доступ",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "зарегистрироваться",
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
},

View File

@ -133,6 +133,7 @@
"Certs": "证书",
"Click to Upload": "点击上传",
"Client IP": "客户端IP",
"Close": "关闭",
"Created time": "创建时间",
"Default application": "默认应用",
"Default application - Tooltip": "默认应用",
@ -165,6 +166,7 @@
"Logo - Tooltip": "应用程序向外展示的图标",
"Master password": "万能密码",
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
"Menu": "目录",
"Method": "方法",
"Model": "模型",
"Model - Tooltip": "Casbin模型",
@ -277,12 +279,12 @@
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号",
"Sign In": "登录",
"Sign in with WebAuthn": "WebAuthn登录",
"Sign in with code": "验证码登录",
"Sign in with password": "密码登录",
"Sign in with {type}": "{type}登录",
"Signing in...": "正在登录...",
"The input is not valid Email or Phone!": "您输入的电子邮箱格式或手机号有误!",
"To access": "访问",
"Verification Code": "验证码",
"WebAuthn": "Web身份验证",
"sign up now": "立即注册",
"username, Email or phone": "用户名、Email或手机号"
},
@ -702,7 +704,7 @@
"Unlink": "解绑",
"Upload (.xlsx)": "上传(.xlsx",
"Upload a photo": "上传头像",
"WebAuthn credentials": "WebAuthn credentials",
"WebAuthn credentials": "WebAuthn凭据",
"input password": "输入密码"
},
"webhook": {