Compare commits

...

26 Commits

Author SHA1 Message Date
HGZ-20
bfa2ab63ad feat: Support metamask mobile login (#2844) 2024-03-30 00:08:52 +08:00
DacongDA
505054b0eb feat: use minWidth for a better display effect in org select (#2843) 2024-03-29 15:47:27 +08:00
Yang Luo
f95ce13b82 fix: support "Email or Phone" in signup table 2024-03-29 09:07:37 +08:00
xyt
5315f16a48 feat: can specify UI theme via /?theme=default and /?theme=dark (#2842)
* feat: set themeType through URL parameter

* Update App.js

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-29 00:52:18 +08:00
Will.Feng
d054f3e001 feat: The /login/oauth/access_token api supports the token and id_token grant types. (#2836)
* In the response of the /api/get-captcha endpoint, add the parameters "owner" and "name" because these two parameters will be used when calling the /api/verify-captcha endpoint.

* The /login/oauth/access_token api supports the token and id_token grant types.
2024-03-28 00:41:54 +08:00
Yang Luo
b158b840bd Add "new-user" to webhook event list 2024-03-27 15:23:06 +08:00
Yang Luo
b16f1807b3 fix: fix bug in "new-user" record 2024-03-27 15:15:40 +08:00
Yang Luo
d0cce1bf7a Order by "id" in GetPaginationRecords() 2024-03-27 15:14:41 +08:00
Yang Luo
9892cd20ab Improve erorr message in CheckVerificationCode() 2024-03-27 15:14:20 +08:00
Yang Luo
d1f31dd327 feat: fix linter 2024-03-26 23:24:53 +08:00
Yang Luo
94743246a1 Improve "%{user.friendlyName}" handling 2024-03-25 21:26:36 +08:00
Yang Luo
39ad1bc593 Add signup's object in AfterRecordMessage() 2024-03-25 21:20:33 +08:00
Will.Feng
d97f833d2a feat: Add 'owner' and 'name' Parameters to /api/get-captcha Response for /api/verify-captcha Usage (#2834) 2024-03-25 16:34:42 +08:00
Yang Luo
948fa911e2 feat: add users to getGroups() and getGroup() APIs 2024-03-22 23:32:30 +08:00
Yang Luo
6073a0f63d Rename GroupListPage and GroupEditPage 2024-03-22 23:14:05 +08:00
Yang Luo
91268bca70 Improve enableAutoSignin option UI 2024-03-22 22:55:10 +08:00
DacongDA
23dbb0b926 feat: add response to Records page (#2830)
* feat: add response to Records page

* feat: improve AddRecord

* feat: remove log and return err

* feat: improve record in signup and record deny

* fix: filter will generate 403 record correctly
2024-03-22 14:53:38 +08:00
pfeak
97cc1f9e2b fix: delete duplicate err check in utils/validation.go (#2831) 2024-03-21 18:17:38 +08:00
Yang Luo
8c415be7c7 feat: upgrade goth to v1.79.0 2024-03-20 19:57:15 +08:00
Yang Luo
e87165cfc8 Upgrade go.mod versions 2024-03-20 19:51:56 +08:00
DacongDA
fc4fa2e8b6 feat: add verification list page and related API (#2822)
* feat: add verification list page and relevant api

* feat: improve code format

* fix: fix timestamp display error
2024-03-19 19:10:52 +08:00
Yaodong Yu
44ae76503e feat: add default user mapping in custom oauth2 provider (#2819) 2024-03-18 23:01:17 +08:00
Yang Luo
ae1634a4d5 feat: fix user cannot logout issue about bug in GetSessionToken() 2024-03-18 02:11:39 +08:00
HGZ-20
bdf9864f69 fix: add FaceIdSigninBegin() to verify user information before face login (#2815)
* feat: add FaceIdSigninBegin() to verify user information before face login

* Update face.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-18 00:04:12 +08:00
Yang Luo
72839d6bf5 feat: fix TokenFormat error in get-account API 2024-03-17 23:03:50 +08:00
Will.Feng
2c4b1093ed fix: Correct expiresIn calculation for WeChat Mini Program token. (#2814) 2024-03-17 22:20:21 +08:00
66 changed files with 3023 additions and 364 deletions

View File

@@ -98,6 +98,7 @@ p, *, *, GET, /api/get-all-objects, *, *
p, *, *, GET, /api/get-all-actions, *, *
p, *, *, GET, /api/get-all-roles, *, *
p, *, *, GET, /api/get-invitation-info, *, *
p, *, *, GET, /api/faceid-signin-begin, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@@ -44,6 +44,8 @@ type Response struct {
}
type Captcha struct {
Owner string `json:"owner"`
Name string `json:"name"`
Type string `json:"type"`
AppKey string `json:"appKey"`
Scene string `json:"scene"`
@@ -271,10 +273,8 @@ func (c *ApiController) Signup() {
return
}
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
c.Ctx.Input.SetParam("recordUserId", user.GetId())
c.Ctx.Input.SetParam("recordSignup", "true")
userId := user.GetId()
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
@@ -435,16 +435,16 @@ func (c *ApiController) GetAccount() {
return
}
token := c.GetSessionToken()
if token == nil {
token, err = object.GetTokenForExtension(user, c.Ctx.Request.Host)
accessToken := c.GetSessionToken()
if accessToken == "" {
accessToken, err = object.GetAccessTokenByUser(user, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionToken(token)
c.SetSessionToken(accessToken)
}
u.AccessToken = token.AccessToken
u.AccessToken = accessToken
resp := Response{
Status: "ok",
@@ -532,10 +532,12 @@ func (c *ApiController) GetCaptcha() {
return
}
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
c.ResponseOk(Captcha{Owner: captchaProvider.Owner, Name: captchaProvider.Name, Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
return
} else if captchaProvider.Type != "" {
c.ResponseOk(Captcha{
Owner: captchaProvider.Owner,
Name: captchaProvider.Name,
Type: captchaProvider.Type,
SubType: captchaProvider.SubType,
ClientId: captchaProvider.ClientId,

View File

@@ -508,10 +508,7 @@ func (c *ApiController) Login() {
resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
c.Ctx.Input.SetParam("recordUserId", user.GetId())
}
} else if authForm.Provider != "" {
var application *object.Application
@@ -632,10 +629,7 @@ func (c *ApiController) Login() {
}
resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
c.Ctx.Input.SetParam("recordUserId", user.GetId())
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
// Sign up via OAuth
if application.EnableLinkWithEmail {
@@ -768,16 +762,8 @@ func (c *ApiController) Login() {
resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
record2 := object.NewRecord(c.Ctx)
record2.Action = "signup"
record2.Organization = application.Organization
record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) })
c.Ctx.Input.SetParam("recordUserId", user.GetId())
c.Ctx.Input.SetParam("recordSignup", "true")
} else if provider.Category == "SAML" {
// TODO: since we get the user info from SAML response, we can try to create the user
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
@@ -879,10 +865,7 @@ func (c *ApiController) Login() {
resp = c.HandleLoggedIn(application, user, &authForm)
c.setMfaUserSession("")
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
c.Ctx.Input.SetParam("recordUserId", user.GetId())
} else {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
@@ -901,10 +884,7 @@ func (c *ApiController) Login() {
user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
c.Ctx.Input.SetParam("recordUserId", user.GetId())
} else {
c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), form = %s"), util.StructToJson(authForm)))
return

View File

@@ -122,15 +122,13 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionToken() *object.Token {
tokenValue := c.GetSession("token")
var token *object.Token
var ok bool
if token, ok = tokenValue.(*object.Token); !ok {
token = nil
func (c *ApiController) GetSessionToken() string {
accessToken := c.GetSession("accessToken")
if accessToken == nil {
return ""
}
return token
return accessToken.(string)
}
func (c *ApiController) GetSessionApplication() *object.Application {
@@ -153,7 +151,7 @@ func (c *ApiController) ClearUserSession() {
}
func (c *ApiController) ClearTokenSession() {
c.SetSessionToken(nil)
c.SetSessionToken("")
}
func (c *ApiController) GetSessionOidc() (string, string) {
@@ -182,8 +180,8 @@ func (c *ApiController) SetSessionUsername(user string) {
c.SetSession("username", user)
}
func (c *ApiController) SetSessionToken(token *object.Token) {
c.SetSession("token", token)
func (c *ApiController) SetSessionToken(accessToken string) {
c.SetSession("accessToken", accessToken)
}
// GetSessionData ...

View File

@@ -68,7 +68,7 @@ func (c *ApiController) GetCerts() {
// GetGlobalCerts
// @Title GetGlobalCerts
// @Tag Cert API
// @Description get globle certs
// @Description get global certs
// @Success 200 {array} object.Cert The Response object
// @router /get-global-certs [get]
func (c *ApiController) GetGlobalCerts() {

55
controllers/face.go Normal file
View File

@@ -0,0 +1,55 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Casdoor will expose its providers as services to SDK
// We are going to implement those services as APIs here
package controllers
import (
"fmt"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// FaceIDSigninBegin
// @Title FaceIDSigninBegin
// @Tag Login API
// @Description FaceId Login Flow 1st stage
// @Param owner query string true "owner"
// @Param name query string true "name"
// @Success 200 {object} controllers.Response The Response object
// @router /faceid-signin-begin [get]
func (c *ApiController) FaceIDSigninBegin() {
userOwner := c.Input().Get("owner")
userName := c.Input().Get("name")
user, err := object.GetUserByFields(userOwner, userName)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
return
}
if len(user.FaceIds) == 0 {
c.ResponseError(c.T("check:Face data does not exist, cannot log in"))
return
}
c.ResponseOk()
}

View File

@@ -43,13 +43,20 @@ func (c *ApiController) GetGroups() {
if err != nil {
c.ResponseError(err.Error())
return
} else {
if withTree == "true" {
c.ResponseOk(object.ConvertToTreeData(groups, owner))
return
}
c.ResponseOk(groups)
}
err = object.ExtendGroupsWithUsers(groups)
if err != nil {
c.ResponseError(err.Error())
return
}
if withTree == "true" {
c.ResponseOk(object.ConvertToTreeData(groups, owner))
return
}
c.ResponseOk(groups)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGroupCount(owner, field, value)
@@ -64,6 +71,12 @@ func (c *ApiController) GetGroups() {
c.ResponseError(err.Error())
return
} else {
err = object.ExtendGroupsWithUsers(groups)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(groups, paginator.Nums())
}
}
@@ -84,6 +97,13 @@ func (c *ApiController) GetGroup() {
c.ResponseError(err.Error())
return
}
err = object.ExtendGroupWithUsers(group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(group)
}

View File

@@ -125,9 +125,11 @@ func (c *ApiController) SendEmail() {
return
}
userString := "Hi"
if user != nil {
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
userString = user.GetFriendlyName()
}
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
}
}

View File

@@ -164,6 +164,7 @@ func (c *ApiController) GetOAuthToken() {
code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier")
scope := c.Input().Get("scope")
nonce := c.Input().Get("nonce")
username := c.Input().Get("username")
password := c.Input().Get("password")
tag := c.Input().Get("tag")
@@ -197,6 +198,9 @@ func (c *ApiController) GetOAuthToken() {
if scope == "" {
scope = tokenRequest.Scope
}
if nonce == "" {
nonce = tokenRequest.Nonce
}
if username == "" {
username = tokenRequest.Username
}
@@ -216,7 +220,7 @@ func (c *ApiController) GetOAuthToken() {
}
host := c.Ctx.Request.Host
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -21,6 +21,7 @@ type TokenRequest struct {
Code string `json:"code"`
Verifier string `json:"code_verifier"`
Scope string `json:"scope"`
Nonce string `json:"nonce"`
Username string `json:"username"`
Password string `json:"password"`
Tag string `json:"tag"`

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"strings"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/form"
"github.com/casdoor/casdoor/object"
@@ -35,6 +36,90 @@ const (
MfaAuthVerification = "mfaAuth"
)
// GetVerifications
// @Title GetVerifications
// @Tag Verification API
// @Description get payments
// @Param owner query string true "The owner of payments"
// @Success 200 {array} object.Verification The Response object
// @router /get-payments [get]
func (c *ApiController) GetVerifications() {
owner := c.Input().Get("owner")
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")
if limit == "" || page == "" {
payments, err := object.GetVerifications(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments)
} else {
limit := util.ParseInt(limit)
count, err := object.GetVerificationCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
payments, err := object.GetPaginationVerifications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments, paginator.Nums())
}
}
// GetUserVerifications
// @Title GetUserVerifications
// @Tag Verification API
// @Description get payments for a user
// @Param owner query string true "The owner of payments"
// @Param organization query string true "The organization of the user"
// @Param user query string true "The username of the user"
// @Success 200 {array} object.Verification The Response object
// @router /get-user-payments [get]
func (c *ApiController) GetUserVerifications() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
payments, err := object.GetUserVerifications(owner, user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments)
}
// GetVerification
// @Title GetVerification
// @Tag Verification API
// @Description get payment
// @Param id query string true "The id ( owner/name ) of the payment"
// @Success 200 {object} object.Verification The Response object
// @router /get-payment [get]
func (c *ApiController) GetVerification() {
id := c.Input().Get("id")
payment, err := object.GetVerification(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment)
}
// SendVerificationCode ...
// @Title SendVerificationCode
// @Tag Verification API

18
go.mod
View File

@@ -19,10 +19,11 @@ require (
github.com/denisenkom/go-mssqldb v0.9.0
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
github.com/ethereum/go-ethereum v1.13.14
github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0
github.com/go-asn1-ber/asn1-ber v1.5.5
github.com/go-git/go-git/v5 v5.6.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72
@@ -32,14 +33,14 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.4.0
github.com/json-iterator/go v1.1.12
github.com/lestrrat-go/jwx v1.2.21
github.com/lestrrat-go/jwx v1.2.29
github.com/lib/pq v1.10.9
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.78.0
github.com/markbates/goth v1.79.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nyaruka/phonenumbers v1.1.5
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_golang v1.12.0
github.com/prometheus/client_model v0.4.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1
@@ -49,19 +50,18 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/stripe/stripe-go/v74 v74.29.0
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/xorm-io/builder v0.3.13
github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.19.0
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.13.0
golang.org/x/crypto v0.21.0
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.17.0
google.golang.org/api v0.150.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0

624
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -98,11 +98,19 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
requiredFields := []string{"id", "username", "displayName"}
for _, field := range requiredFields {
_, ok := idp.UserMapping[field]
if !ok {
return nil, fmt.Errorf("cannot find %s in userMapping, please check your configuration in custom provider", field)
}
}
// map user info
for k, v := range idp.UserMapping {
_, ok := dataMap[v]
if !ok {
return nil, fmt.Errorf("cannot find %s in user from castom provider", v)
return nil, fmt.Errorf("cannot find %s in user from custom provider", v)
}
dataMap[k] = dataMap[v]
}

View File

@@ -15,15 +15,43 @@
package idp
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/ethereum/go-ethereum/crypto"
"golang.org/x/oauth2"
)
type EIP712Message struct {
Domain struct {
ChainId string `json:"chainId"`
Name string `json:"name"`
Version string `json:"version"`
} `json:"domain"`
Message struct {
Prompt string `json:"prompt"`
Nonce string `json:"nonce"`
CreateAt string `json:"createAt"`
} `json:"message"`
PrimaryType string `json:"primaryType"`
Types struct {
EIP712Domain []struct {
Name string `json:"name"`
Type string `json:"type"`
} `json:"EIP712Domain"`
AuthRequest []struct {
Name string `json:"name"`
Type string `json:"type"`
} `json:"AuthRequest"`
} `json:"types"`
}
type MetaMaskIdProvider struct {
Client *http.Client
}
@@ -42,6 +70,15 @@ func (idp *MetaMaskIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil {
return nil, err
}
valid, err := VerifySignature(web3AuthToken.Address, web3AuthToken.TypedData, web3AuthToken.Signature)
if err != nil {
return nil, err
}
if !valid {
return nil, fmt.Errorf("invalid signature")
}
token := &oauth2.Token{
AccessToken: web3AuthToken.Signature,
TokenType: "Bearer",
@@ -68,3 +105,43 @@ func (idp *MetaMaskIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
}
return userInfo, nil
}
func VerifySignature(userAddress string, originalMessage string, signatureHex string) (bool, error) {
var eip712Mes EIP712Message
err := json.Unmarshal([]byte(originalMessage), &eip712Mes)
if err != nil {
return false, fmt.Errorf("invalid signature (Error parsing JSON)")
}
createAtTime, err := time.Parse("2006/1/2 15:04:05", eip712Mes.Message.CreateAt)
currentTime := time.Now()
if createAtTime.Before(currentTime.Add(-1*time.Minute)) && createAtTime.After(currentTime) {
return false, fmt.Errorf("invalid signature (signature does not meet time requirements)")
}
if !strings.HasPrefix(signatureHex, "0x") {
signatureHex = "0x" + signatureHex
}
signatureBytes, err := hex.DecodeString(signatureHex[2:])
if err != nil {
return false, err
}
if signatureBytes[64] != 27 && signatureBytes[64] != 28 {
return false, fmt.Errorf("invalid signature (incorrect recovery id)")
}
signatureBytes[64] -= 27
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len([]byte(originalMessage)), []byte(originalMessage))
hash := crypto.Keccak256Hash([]byte(msg))
pubKey, err := crypto.SigToPub(hash.Bytes(), signatureBytes)
if err != nil {
return false, err
}
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return strings.EqualFold(recoveredAddr.Hex(), userAddress), nil
}

View File

@@ -59,6 +59,7 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.InsertFilter("*", beego.AfterExec, routers.AfterRecordMessage, false)
beego.BConfig.WebConfig.Session.SessionOn = true
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"

View File

@@ -17,6 +17,7 @@ package object
import (
"errors"
"fmt"
"sync"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
@@ -30,13 +31,13 @@ type Group struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Manager string `xorm:"varchar(100)" json:"manager"`
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
Type string `xorm:"varchar(100)" json:"type"`
ParentId string `xorm:"varchar(100)" json:"parentId"`
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
Users []*User `xorm:"-" json:"users"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Manager string `xorm:"varchar(100)" json:"manager"`
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
Type string `xorm:"varchar(100)" json:"type"`
ParentId string `xorm:"varchar(100)" json:"parentId"`
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
Users []string `xorm:"-" json:"users"`
Title string `json:"title,omitempty"`
Key string `json:"key,omitempty"`
@@ -288,6 +289,55 @@ func GetGroupUsers(groupId string) ([]*User, error) {
return users, nil
}
func ExtendGroupWithUsers(group *Group) error {
if group == nil {
return nil
}
users, err := GetUsers(group.Owner)
if err != nil {
return err
}
groupId := group.GetId()
userIds := []string{}
for _, user := range users {
if util.InSlice(user.Groups, groupId) {
userIds = append(userIds, user.GetId())
}
}
group.Users = userIds
return nil
}
func ExtendGroupsWithUsers(groups []*Group) error {
var wg sync.WaitGroup
errChan := make(chan error, len(groups))
for _, group := range groups {
wg.Add(1)
go func(group *Group) {
defer wg.Done()
err := ExtendGroupWithUsers(group)
if err != nil {
errChan <- err
}
}(group)
}
wg.Wait()
close(errChan)
for err := range errChan {
if err != nil {
return err
}
}
return nil
}
func GroupChangeTrigger(oldName, newName string) error {
session := ormer.Engine.NewSession()
defer session.Close()

View File

@@ -45,7 +45,6 @@ func InitDb() {
}
initWebAuthn()
initToken()
}
func getBuiltInAccountItems() []*AccountItem {
@@ -310,10 +309,6 @@ func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}
func initToken() {
gob.Register(&Token{})
}
func initBuiltInUserModel() {
model, err := GetModel("built-in/user-model-built-in")
if err != nil {

View File

@@ -15,6 +15,7 @@
package object
import (
"encoding/json"
"fmt"
"strings"
@@ -34,7 +35,12 @@ type Record struct {
casvisorsdk.Record
}
func NewRecord(ctx *context.Context) *casvisorsdk.Record {
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
}
func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
@@ -47,6 +53,17 @@ func NewRecord(ctx *context.Context) *casvisorsdk.Record {
object = string(ctx.Input.RequestBody)
}
respBytes, err := json.Marshal(ctx.Input.Data()["json"])
if err != nil {
return nil, err
}
var resp Response
err = json.Unmarshal(respBytes, &resp)
if err != nil {
return nil, err
}
language := ctx.Request.Header.Get("Accept-Language")
if len(language) > 2 {
language = language[0:2]
@@ -63,10 +80,10 @@ func NewRecord(ctx *context.Context) *casvisorsdk.Record {
Action: action,
Language: languageCode,
Object: object,
Response: "",
Response: fmt.Sprintf("{status:\"%s\", msg:\"%s\"}", resp.Status, resp.Msg),
IsTriggered: false,
}
return &record
return &record, nil
}
func AddRecord(record *casvisorsdk.Record) bool {
@@ -123,6 +140,12 @@ func GetRecords() ([]*casvisorsdk.Record, error) {
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string, filterRecord *casvisorsdk.Record) ([]*casvisorsdk.Record, error) {
records := []*casvisorsdk.Record{}
if sortField == "" || sortOrder == "" {
sortField = "id"
sortOrder = "descend"
}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&records, filterRecord)
if err != nil {
@@ -142,6 +165,25 @@ func GetRecordsByField(record *casvisorsdk.Record) ([]*casvisorsdk.Record, error
return records, nil
}
func CopyRecord(record *casvisorsdk.Record) *casvisorsdk.Record {
res := &casvisorsdk.Record{
Owner: record.Owner,
Name: record.Name,
CreatedTime: record.CreatedTime,
Organization: record.Organization,
ClientIp: record.ClientIp,
User: record.User,
Method: record.Method,
RequestUri: record.RequestUri,
Action: record.Action,
Language: record.Language,
Object: record.Object,
Response: record.Response,
IsTriggered: record.IsTriggered,
}
return res
}
func getFilteredWebhooks(webhooks []*Webhook, organization string, action string) []*Webhook {
res := []*Webhook{}
for _, webhook := range webhooks {

View File

@@ -359,6 +359,10 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
var token *jwt.Token
var refreshToken *jwt.Token
if application.TokenFormat == "" {
application.TokenFormat = "JWT"
}
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
if application.TokenFormat == "JWT" {
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)

View File

@@ -189,7 +189,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}, nil
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string) (interface{}, error) {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string) (interface{}, error) {
application, err := GetApplicationByClientId(clientId)
if err != nil {
return nil, err
@@ -220,6 +220,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
token, tokenError, err = GetPasswordToken(application, username, password, scope, host)
case "client_credentials": // Client Credentials Grant
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
case "token", "id_token": // Implicit Grant
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
case "refresh_token":
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil {
@@ -582,6 +584,33 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
return token, nil, nil
}
// GetImplicitToken
// Implicit flow
func GetImplicitToken(application *Application, username string, scope string, nonce string, host string) (*Token, *TokenError, error) {
user, err := GetUserByFields(application.Organization, username)
if err != nil {
return nil, nil, err
}
if user == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user does not exist",
}, nil
}
if user.IsForbidden {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}, nil
}
token, err := GetTokenByUser(application, user, scope, nonce, host)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}
// GetTokenByUser
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
@@ -715,7 +744,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
Code: session.SessionKey, // a trick, because miniprogram does not use the code, so use the code field to save the session_key
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: "",
TokenType: "Bearer",
CodeIsUsed: true,
@@ -727,18 +756,19 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
return token, nil, nil
}
func GetTokenForExtension(user *User, host string) (*Token, error) {
func GetAccessTokenByUser(user *User, host string) (string, error) {
application, err := GetApplicationByUser(user)
if err != nil {
return nil, err
return "", err
}
if application == nil {
return nil, fmt.Errorf("the application for user %s is not found", user.Id)
return "", fmt.Errorf("the application for user %s is not found", user.Id)
}
token, err := GetTokenByUser(application, user, "profile", "", host)
if err != nil {
return nil, err
return "", err
}
return token, nil
return token.AccessToken, nil
}

View File

@@ -50,13 +50,13 @@ type VerificationRecord struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
RemoteAddr string `xorm:"varchar(100)"`
Type string `xorm:"varchar(10)"`
User string `xorm:"varchar(100) notnull"`
Provider string `xorm:"varchar(100) notnull"`
Receiver string `xorm:"varchar(100) index notnull"`
Code string `xorm:"varchar(10) notnull"`
Time int64 `xorm:"notnull"`
RemoteAddr string `xorm:"varchar(100)" json:"remoteAddr"`
Type string `xorm:"varchar(10)" json:"type"`
User string `xorm:"varchar(100) notnull" json:"user"`
Provider string `xorm:"varchar(100) notnull" json:"provider"`
Receiver string `xorm:"varchar(100) index notnull" json:"receiver"`
Code string `xorm:"varchar(10) notnull" json:"code"`
Time int64 `xorm:"notnull" json:"time"`
IsUsed bool
}
@@ -92,9 +92,12 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := strings.Replace(provider.Content, "%s", code, 1)
userString := "Hi"
if user != nil {
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
userString = user.GetFriendlyName()
}
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
err := IsAllowSend(user, remoteAddr, provider.Category)
if err != nil {
@@ -187,14 +190,17 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
}
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
if err != nil {
return nil, err
}
now := time.Now().Unix()
if now-record.Time > timeout*60 {
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeout)}, nil
if now-record.Time > timeoutInMinutes*60*10 {
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
}
if now-record.Time > timeoutInMinutes*60 {
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
}
if record.Code != code {
@@ -278,3 +284,62 @@ func getRandomCode(length int) string {
}
return string(result)
}
func GetVerificationCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&VerificationRecord{Owner: owner})
}
func GetVerifications(owner string) ([]*VerificationRecord, error) {
verifications := []*VerificationRecord{}
err := ormer.Engine.Desc("created_time").Find(&verifications, &VerificationRecord{Owner: owner})
if err != nil {
return nil, err
}
return verifications, nil
}
func GetUserVerifications(owner, user string) ([]*VerificationRecord, error) {
verifications := []*VerificationRecord{}
err := ormer.Engine.Desc("created_time").Find(&verifications, &VerificationRecord{Owner: owner, User: user})
if err != nil {
return nil, err
}
return verifications, nil
}
func GetPaginationVerifications(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*VerificationRecord, error) {
verifications := []*VerificationRecord{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&verifications, &VerificationRecord{Owner: owner})
if err != nil {
return nil, err
}
return verifications, nil
}
func getVerification(owner string, name string) (*VerificationRecord, error) {
if owner == "" || name == "" {
return nil, nil
}
verification := VerificationRecord{Owner: owner, Name: name}
existed, err := ormer.Engine.Get(&verification)
if err != nil {
return nil, err
}
if existed {
return &verification, nil
} else {
return nil, nil
}
}
func GetVerification(id string) (*VerificationRecord, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getVerification(owner, name)
}

View File

@@ -20,6 +20,8 @@ import (
"net/http"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/util"
@@ -211,5 +213,17 @@ func ApiFilter(ctx *context.Context) {
if !isAllowed {
denyRequest(ctx)
record, err := object.NewRecord(ctx)
if err != nil {
return
}
record.Organization = subOwner
record.User = subName // auth:Unauthorized operation
record.Response = fmt.Sprintf("{status:\"error\", msg:\"%s\"}", T(ctx, "auth:Unauthorized operation"))
util.SafeGoroutine(func() {
object.AddRecord(record)
})
}
}

View File

@@ -15,9 +15,12 @@
package routers
import (
"fmt"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
)
func getUser(ctx *context.Context) (username string) {
@@ -60,12 +63,49 @@ func RecordMessage(ctx *context.Context) {
return
}
record := object.NewRecord(ctx)
userId := getUser(ctx)
ctx.Input.SetParam("recordUserId", userId)
}
func AfterRecordMessage(ctx *context.Context) {
record, err := object.NewRecord(ctx)
if err != nil {
fmt.Printf("AfterRecordMessage() error: %s\n", err.Error())
return
}
userId := ctx.Input.Params()["recordUserId"]
if userId != "" {
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
}
util.SafeGoroutine(func() { object.AddRecord(record) })
var record2 *casvisorsdk.Record
recordSignup := ctx.Input.Params()["recordSignup"]
if recordSignup == "true" {
record2 = object.CopyRecord(record)
record2.Action = "new-user"
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
fmt.Printf("AfterRecordMessage() error: %s\n", err.Error())
return
}
if user == nil {
err = fmt.Errorf("the user: %s is not found", userId)
fmt.Printf("AfterRecordMessage() error: %s\n", err.Error())
return
}
record2.Object = util.StructToJson(user)
}
util.SafeGoroutine(func() {
object.AddRecord(record)
if record2 != nil {
object.AddRecord(record2)
}
})
}

View File

@@ -255,6 +255,7 @@ func initAPI() {
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
beego.Router("/api/get-verifications", &controllers.ApiController{}, "GET:GetVerifications")
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
@@ -300,4 +301,6 @@ func initAPI() {
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
beego.Router("/scim/*", &controllers.RootController{}, "*:HandleScim")
beego.Router("/api/faceid-signin-begin", &controllers.ApiController{}, "GET:FaceIDSigninBegin")
}

View File

@@ -85,9 +85,6 @@ func GetCountryCode(prefix string, phone string) (string, error) {
if err != nil {
return "", err
}
if err != nil {
return "", err
}
countryCode := phonenumbers.GetRegionCodeForNumber(phoneNumber)
if countryCode == "" {

View File

@@ -10,6 +10,7 @@
"@ctrl/tinycolor": "^3.5.0",
"@emotion/react": "^11.10.5",
"@metamask/eth-sig-util": "^6.0.0",
"@metamask/sdk-react": "^0.18.0",
"@web3-onboard/coinbase": "^2.2.5",
"@web3-onboard/core": "^2.20.5",
"@web3-onboard/frontier": "^2.0.4",

View File

@@ -41,6 +41,7 @@ setTwoToneColor("rgb(87,52,211)");
class App extends Component {
constructor(props) {
super(props);
this.setThemeAlgorithm();
let storageThemeAlgorithm = [];
try {
storageThemeAlgorithm = localStorage.getItem("themeAlgorithm") ? JSON.parse(localStorage.getItem("themeAlgorithm")) : ["default"];
@@ -157,6 +158,15 @@ class App extends Component {
return Setting.getLogo(themes);
}
setThemeAlgorithm() {
const currentUrl = window.location.href;
const url = new URL(currentUrl);
const themeType = url.searchParams.get("theme");
if (themeType === "dark" || themeType === "default") {
localStorage.setItem("themeAlgorithm", JSON.stringify([themeType]));
}
}
setLanguage(account) {
const language = account?.language;
if (language !== null && language !== "" && language !== i18next.language) {

View File

@@ -456,6 +456,10 @@ class ApplicationEditPage extends React.Component {
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
if (!checked) {
this.updateApplicationField("enableAutoSignin", false);
}
this.updateApplicationField("enableSigninSession", checked);
}} />
</Col>
@@ -466,6 +470,11 @@ class ApplicationEditPage extends React.Component {
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableAutoSignin} onChange={checked => {
if (!this.state.application.enableSigninSession && checked) {
Setting.showMessage("error", i18next.t("application:Please enable \"Signin session\" first before enabling \"Auto signin\""));
return;
}
this.updateApplicationField("enableAutoSignin", checked);
}} />
</Col>

View File

@@ -177,6 +177,16 @@ class GroupEditPage extends React.Component {
)} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Users"), i18next.t("general:Users - Tooltip"))} :
</Col>
<Col style={{marginTop: "5px"}} span={22} >
{
Setting.getTags(this.state.group.users, "users")
}
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :

View File

@@ -195,6 +195,17 @@ class GroupListPage extends BaseListPage {
</Link>;
},
},
{
title: i18next.t("general:Users"),
dataIndex: "users",
key: "users",
// width: "200px",
sorter: true,
...this.getColumnSearchProps("users"),
render: (text, record, index) => {
return Setting.getTags(text, "users");
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",

View File

@@ -34,8 +34,8 @@ import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
import UserListPage from "./UserListPage";
import GroupTreePage from "./GroupTreePage";
import GroupListPage from "./GroupList";
import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupListPage";
import GroupEditPage from "./GroupEditPage";
import UserEditPage from "./UserEditPage";
import InvitationListPage from "./InvitationListPage";
import InvitationEditPage from "./InvitationEditPage";
@@ -92,6 +92,7 @@ import * as AuthBackend from "./auth/AuthBackend";
import {clearWeb3AuthToken} from "./auth/Web3Auth";
import TransactionListPage from "./TransactionListPage";
import TransactionEditPage from "./TransactionEditPage";
import VerificationListPage from "./VerificationListPage";
function ManagementPage(props) {
@@ -293,6 +294,7 @@ function ManagementPage(props) {
Conf.CasvisorUrl ? Setting.getItem(<a target="_blank" rel="noreferrer" href={Conf.CasvisorUrl}>{i18next.t("general:Records")}</a>, "/records")
: Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
Setting.getItem(<Link to="/verifications">{i18next.t("general:Verifications")}</Link>, "/verifications"),
]));
res.push(Setting.getItem(<Link style={{color: textColor}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone twoToneColor={twoToneColor} />, [
@@ -360,6 +362,7 @@ function ManagementPage(props) {
<Route exact path="/resources" render={(props) => renderLoginIfNotLoggedIn(<ResourceListPage account={account} {...props} />)} />
<Route exact path="/certs" render={(props) => renderLoginIfNotLoggedIn(<CertListPage account={account} {...props} />)} />
<Route exact path="/certs/:organizationName/:certName" render={(props) => renderLoginIfNotLoggedIn(<CertEditPage account={account} {...props} />)} />
<Route exact path="/verifications" render={(props) => renderLoginIfNotLoggedIn(<VerificationListPage account={account} {...props} />)} />
<Route exact path="/roles" render={(props) => renderLoginIfNotLoggedIn(<RoleListPage account={account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => renderLoginIfNotLoggedIn(<RoleEditPage account={account} {...props} />)} />
<Route exact path="/permissions" render={(props) => renderLoginIfNotLoggedIn(<PermissionListPage account={account} {...props} />)} />

View File

@@ -40,6 +40,14 @@ require("codemirror/mode/css/css");
const {Option} = Select;
const {TextArea} = Input;
const defaultUserMapping = {
id: "id",
username: "username",
displayName: "displayName",
email: "email",
avatarUrl: "avatarUrl",
};
class ProviderEditPage extends React.Component {
constructor(props) {
super(props);
@@ -70,7 +78,7 @@ class ProviderEditPage extends React.Component {
if (res.status === "ok") {
const provider = res.data;
provider.userMapping = provider.userMapping || {};
provider.userMapping = provider.userMapping || defaultUserMapping;
this.setState({
provider: provider,
});
@@ -141,8 +149,16 @@ class ProviderEditPage extends React.Component {
}
updateUserMappingField(key, value) {
const requiredKeys = ["id", "username", "displayName"];
const provider = this.state.provider;
if (value === "" && requiredKeys.includes(key)) {
Setting.showMessage("error", i18next.t("provider:This field is required"));
return;
}
provider.userMapping[key] = value;
this.setState({
provider: provider,
});
@@ -1321,6 +1337,20 @@ class ProviderEditPage extends React.Component {
</Row>
) : null
}
{
this.state.provider.type === "MetaMask" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Signature messages"), i18next.t("provider:Signature messages - Tooltip"))} :
</Col>
<Col span={22}>
<Input value={this.state.provider.metadata} onChange={e => {
this.updateProviderField("metadata", e.target.value);
}} />
</Col>
</Row>
) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :

View File

@@ -151,6 +151,14 @@ class RecordListPage extends BaseListPage {
sorter: true,
...this.getColumnSearchProps("language"),
},
{
title: i18next.t("record:Response"),
dataIndex: "response",
key: "response",
width: "90px",
sorter: true,
...this.getColumnSearchProps("response"),
},
{
title: i18next.t("record:Object"),
dataIndex: "object",
@@ -179,7 +187,7 @@ class RecordListPage extends BaseListPage {
sorter: true,
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
if (!["signup", "login", "logout", "update-user"].includes(record.action)) {
if (!["signup", "login", "logout", "update-user", "new-user"].includes(record.action)) {
return null;
}

View File

@@ -0,0 +1,187 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import BaseListPage from "./BaseListPage";
import * as Setting from "./Setting";
import moment from "moment/moment";
import * as VerificationBackend from "./backend/VerificationBackend";
import i18next from "i18next";
import {Link} from "react-router-dom";
import React from "react";
import {Table} from "antd";
class VerificationListPage extends BaseListPage {
newVerification() {
const randomName = Setting.getRandomName();
return {
owner: "admin",
name: `Verification_${randomName}`,
createdTime: moment().format(),
};
}
renderTable(verifications) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "150px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/syncers/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("provider:Type"),
dataIndex: "type",
key: "type",
width: "120px",
sorter: true,
...this.getColumnSearchProps("type"),
},
{
title: i18next.t("general:User"),
dataIndex: "user",
key: "user",
width: "120px",
sorter: true,
...this.getColumnSearchProps("user"),
render: (text, record, index) => {
return (
<Link to={`/users/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Provider"),
dataIndex: "provider",
key: "provider",
width: "150px",
sorter: true,
...this.getColumnSearchProps("provider"),
render: (text, record, index) => {
return (
<Link to={`/providers/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("verification:Receiver"),
dataIndex: "receiver",
key: "receiver",
width: "120px",
sorter: true,
...this.getColumnSearchProps("receiver"),
},
{
title: i18next.t("login:Verification code"),
dataIndex: "code",
key: "code",
width: "120px",
sorter: true,
...this.getColumnSearchProps("code"),
},
{
title: i18next.t("general:Timestamp"),
dataIndex: "time",
key: "time",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text * 1000);
},
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
key: "createdTime",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
];
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={verifications} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Verifications")}&nbsp;&nbsp;&nbsp;&nbsp;
</div>
)}
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.type !== undefined && params.type !== null) {
field = "type";
value = params.type;
}
this.setState({loading: true});
VerificationBackend.getVerifications("admin", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
};
}
export default VerificationListPage;

View File

@@ -275,7 +275,7 @@ class WebhookEditPage extends React.Component {
}} >
{
(
["signup", "login", "logout"].concat(this.getApiPaths()).map((option, index) => {
["signup", "login", "logout", "new-user"].concat(this.getApiPaths()).map((option, index) => {
return (
<Option key={option} value={option}>{option}</Option>
);

View File

@@ -346,10 +346,28 @@ class LoginPage extends React.Component {
return;
}
if (this.state.loginMethod === "faceId") {
this.setState({
openFaceRecognitionModal: true,
values: values,
});
let username = this.state.username;
if (username === null || username === "") {
username = values["username"];
}
const application = this.getApplicationObj();
fetch(`${Setting.ServerUrl}/api/faceid-signin-begin?owner=${application.organization}&name=${username}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json())
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
openFaceRecognitionModal: true,
values: values,
});
});
return;
}
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
@@ -666,7 +684,8 @@ class LoginPage extends React.Component {
>
{
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
i18next.t("login:Sign In")
this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
i18next.t("login:Sign In")
}
</Button>
{
@@ -1154,7 +1173,7 @@ class LoginPage extends React.Component {
};
return (
<div style={{height: 300}}>
<div style={{height: 300, minWidth: 320}}>
{renderChoiceBox()}
</div>
);

View File

@@ -0,0 +1,107 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {getAuthUrl} from "./Provider";
import {getProviderLogoURL, goToLink, showMessage} from "../Setting";
import i18next from "i18next";
import {
generateNonce,
getWeb3AuthTokenKey,
setWeb3AuthToken
} from "./Web3Auth";
import {useSDK} from "@metamask/sdk-react";
import React, {useEffect} from "react";
export function MetaMaskLoginButton(props) {
const {application, web3Provider, method, width, margin} = props;
const {sdk, chainId, account} = useSDK();
const [typedData, setTypedData] = React.useState("");
const [nonce, setNonce] = React.useState("");
const [signature, setSignature] = React.useState();
useEffect(() => {
if (account && signature) {
const date = new Date();
const token = {
address: account,
nonce: nonce,
createAt: Math.floor(date.getTime() / 1000),
typedData: typedData,
signature: signature,
};
setWeb3AuthToken(token);
const redirectUri = `${getAuthUrl(application, web3Provider, method)}&web3AuthTokenKey=${getWeb3AuthTokenKey(account)}`;
goToLink(redirectUri);
}
}, [account, signature]);
const handleConnectAndSign = async() => {
try {
terminate();
const date = new Date();
const nonce = generateNonce();
setNonce(nonce);
const prompt = web3Provider?.metadata === "" ? "Casdoor: In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way." : web3Provider.metadata;
const typedData = JSON.stringify({
domain: {
chainId: chainId,
name: "Casdoor",
version: "1",
},
message: {
prompt: `${prompt}`,
nonce: nonce,
createAt: `${date.toLocaleString()}`,
},
primaryType: "AuthRequest",
types: {
EIP712Domain: [
{name: "name", type: "string"},
{name: "version", type: "string"},
{name: "chainId", type: "uint256"},
],
AuthRequest: [
{name: "prompt", type: "string"},
{name: "nonce", type: "string"},
{name: "createAt", type: "string"},
],
},
});
setTypedData(typedData);
const sig = await sdk.connectAndSign({msg: typedData});
setSignature(sig);
} catch (err) {
showMessage("error", `${i18next.t("login:Failed to obtain MetaMask authorization")}: ${err.message}`);
}
};
const terminate = () => {
sdk?.terminate();
};
return (
<a key={web3Provider.displayName} onClick={handleConnectAndSign}>
<img width={width} height={width} src={getProviderLogoURL(web3Provider)} alt={web3Provider.displayName}
className="provider-img" style={{margin: margin}} />
</a>
);
}
export default MetaMaskLoginButton;

View File

@@ -43,6 +43,8 @@ import DouyinLoginButton from "./DouyinLoginButton";
import LoginButton from "./LoginButton";
import * as AuthBackend from "./AuthBackend";
import {WechatOfficialAccountModal} from "./Util";
import {MetaMaskProvider} from "@metamask/sdk-react";
import MetaMaskLoginButton from "./MetaMaskLoginButton";
function getSigninButton(provider) {
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName !== "" ? provider.displayName : provider.type);
@@ -160,11 +162,36 @@ export function renderProviderLogo(provider, application, width, margin, size, l
</a>
);
} else if (provider.category === "Web3") {
return (
<a key={provider.displayName} onClick={() => goToWeb3Url(application, provider, "signup")}>
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} className="provider-img" style={{margin: margin}} />
</a>
);
if (provider.type === "MetaMask") {
return (
<MetaMaskProvider
debug={false}
sdkOptions={{
communicationServerUrl: process.env.REACT_APP_COMM_SERVER_URL,
checkInstallationImmediately: false, // This will automatically connect to MetaMask on page load
dappMetadata: {
name: "Casdoor",
url: window.location.protocol + "//" + window.location.host,
},
}}
>
<MetaMaskLoginButton
application={application}
web3Provider={provider}
method={"signup"}
width={width}
margin={margin}
/>
</MetaMaskProvider>
);
} else {
return (
<a key={provider.displayName} onClick={() => goToWeb3Url(application, provider, "signup")}>
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName}
className="provider-img" style={{margin: margin}} />
</a>
);
}
}
} else if (provider.type === "Custom") {
// style definition

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Form, Input, Result} from "antd";
import {Button, Form, Input, Radio, Result, Row} from "antd";
import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton";
@@ -71,6 +71,7 @@ class SignupPage extends React.Component {
applicationName: (props.applicationName ?? props.match?.params?.applicationName) ?? null,
email: "",
phone: "",
emailOrPhoneMode: "",
countryCode: "",
emailCode: "",
phoneCode: "",
@@ -360,130 +361,176 @@ class SignupPage extends React.Component {
<RegionSelect onChange={(value) => {this.setState({region: value});}} />
</Form.Item>
);
} else if (signupItem.name === "Email") {
return (
<React.Fragment>
<Form.Item
name="email"
label={signupItem.label ? signupItem.label : i18next.t("general:Email")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your Email!"),
},
{
validator: (_, value) => {
if (this.state.email !== "" && !Setting.isValidEmail(this.state.email)) {
this.setState({validEmail: false});
return Promise.reject(i18next.t("signup:The input is not valid Email!"));
}
this.setState({validEmail: true});
return Promise.resolve();
},
},
]}
>
<Input placeholder={signupItem.placeholder} disabled={this.state.invitation !== undefined && this.state.invitation.email !== ""} onChange={e => this.setState({email: e.target.value})} />
</Form.Item>
{
signupItem.rule !== "No verification" &&
} else if (signupItem.name === "Email" || signupItem.name === "Phone" || signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
const renderEmailItem = () => {
return (
<React.Fragment>
<Form.Item
name="emailCode"
label={signupItem.label ? signupItem.label : i18next.t("code:Email code")}
rules={[{
required: required,
message: i18next.t("code:Please input your verification code!"),
}]}
>
<SendCodeInput
disabled={!this.state.validEmail}
method={"signup"}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]}
application={application}
/>
</Form.Item>
}
</React.Fragment>
);
} else if (signupItem.name === "Phone") {
return (
<React.Fragment>
<Form.Item label={signupItem.label ? signupItem.label : i18next.t("general:Phone")} required={required}>
<Input.Group compact>
<Form.Item
name="countryCode"
noStyle
rules={[
{
required: required,
message: i18next.t("signup:Please select your country code!"),
},
]}
>
<CountryCodeSelect
style={{width: "35%"}}
countryCodes={this.getApplicationObj().organizationObj.countryCodes}
/>
</Form.Item>
<Form.Item
name="phone"
dependencies={["countryCode"]}
noStyle
rules={[
{
required: required,
message: i18next.t("signup:Please input your phone number!"),
},
({getFieldValue}) => ({
validator: (_, value) => {
if (!required && !value) {
return Promise.resolve();
}
if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validPhone: true});
return Promise.resolve();
},
}),
]}
>
<Input
placeholder={signupItem.placeholder}
style={{width: "65%"}}
disabled={this.state.invitation !== undefined && this.state.invitation.phone !== ""}
onChange={e => this.setState({phone: e.target.value})}
/>
</Form.Item>
</Input.Group>
</Form.Item>
{
signupItem.rule !== "No verification" &&
<Form.Item
name="phoneCode"
label={signupItem.label ? signupItem.label : i18next.t("code:Phone code")}
name="email"
label={signupItem.label ? signupItem.label : i18next.t("general:Email")}
rules={[
{
required: required,
message: i18next.t("code:Please input your phone verification code!"),
message: i18next.t("signup:Please input your Email!"),
},
{
validator: (_, value) => {
if (this.state.email !== "" && !Setting.isValidEmail(this.state.email)) {
this.setState({validEmail: false});
return Promise.reject(i18next.t("signup:The input is not valid Email!"));
}
this.setState({validEmail: true});
return Promise.resolve();
},
},
]}
>
<SendCodeInput
disabled={!this.state.validPhone}
method={"signup"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application}
countryCode={this.form.current?.getFieldValue("countryCode")}
/>
<Input placeholder={signupItem.placeholder} disabled={this.state.invitation !== undefined && this.state.invitation.email !== ""} onChange={e => this.setState({email: e.target.value})} />
</Form.Item>
}
</React.Fragment>
);
{
signupItem.rule !== "No verification" &&
<Form.Item
name="emailCode"
label={signupItem.label ? signupItem.label : i18next.t("code:Email code")}
rules={[{
required: required,
message: i18next.t("code:Please input your verification code!"),
}]}
>
<SendCodeInput
disabled={!this.state.validEmail}
method={"signup"}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]}
application={application}
/>
</Form.Item>
}
</React.Fragment>
);
};
const renderPhoneItem = () => {
return (
<React.Fragment>
<Form.Item label={signupItem.label ? signupItem.label : i18next.t("general:Phone")} required={required}>
<Input.Group compact>
<Form.Item
name="countryCode"
noStyle
rules={[
{
required: required,
message: i18next.t("signup:Please select your country code!"),
},
]}
>
<CountryCodeSelect
style={{width: "35%"}}
countryCodes={this.getApplicationObj().organizationObj.countryCodes}
/>
</Form.Item>
<Form.Item
name="phone"
dependencies={["countryCode"]}
noStyle
rules={[
{
required: required,
message: i18next.t("signup:Please input your phone number!"),
},
({getFieldValue}) => ({
validator: (_, value) => {
if (!required && !value) {
return Promise.resolve();
}
if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validPhone: true});
return Promise.resolve();
},
}),
]}
>
<Input
placeholder={signupItem.placeholder}
style={{width: "65%"}}
disabled={this.state.invitation !== undefined && this.state.invitation.phone !== ""}
onChange={e => this.setState({phone: e.target.value})}
/>
</Form.Item>
</Input.Group>
</Form.Item>
{
signupItem.rule !== "No verification" &&
<Form.Item
name="phoneCode"
label={signupItem.label ? signupItem.label : i18next.t("code:Phone code")}
rules={[
{
required: required,
message: i18next.t("code:Please input your phone verification code!"),
},
]}
>
<SendCodeInput
disabled={!this.state.validPhone}
method={"signup"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application}
countryCode={this.form.current?.getFieldValue("countryCode")}
/>
</Form.Item>
}
</React.Fragment>
);
};
if (signupItem.name === "Email") {
return renderEmailItem();
} else if (signupItem.name === "Phone") {
return renderPhoneItem();
} else if (signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
let emailOrPhoneMode = this.state.emailOrPhoneMode;
if (emailOrPhoneMode === "") {
emailOrPhoneMode = signupItem.name === "Email or Phone" ? "Email" : "Phone";
}
return (
<React.Fragment>
<Row style={{marginTop: "30px", marginBottom: "20px"}} >
<Radio.Group style={{width: "400px"}} buttonStyle="solid" onChange={e => {
this.setState({
emailOrPhoneMode: e.target.value,
});
}} value={emailOrPhoneMode}>
{
signupItem.name === "Email or Phone" ? (
<React.Fragment>
<Radio.Button value={"Email"}>{i18next.t("general:Email")}</Radio.Button>
<Radio.Button value={"Phone"}>{i18next.t("general:Phone")}</Radio.Button>
</React.Fragment>
) : (
<React.Fragment>
<Radio.Button value={"Phone"}>{i18next.t("general:Phone")}</Radio.Button>
<Radio.Button value={"Email"}>{i18next.t("general:Email")}</Radio.Button>
</React.Fragment>
)
}
</Radio.Group>
</Row>
{
emailOrPhoneMode === "Email" ? renderEmailItem() : renderPhoneItem()
}
</React.Fragment>
);
} else {
return null;
}
} else if (signupItem.name === "Password") {
return (
<Form.Item

View File

@@ -0,0 +1,35 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getVerifications(owner, organization, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-verifications?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 getVerification(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-verification?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags, denen der Benutzer angehört, standardmäßig auf \"normaler Benutzer\" festgelegt",
"Users": "Benutzer",
"Users under all organizations": "Benutzer unter allen Organisationen",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "leere",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Umleitung, bitte warten.",
"Sign In": "Anmelden",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Melden Sie sich mit WebAuthn an",
"Sign in with {type}": "Melden Sie sich mit {type} an",
"Signing in...": "Anmelden...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "E-Mail-Adresse zum Empfangen von Test-E-Mails",
"Test SMTP Connection": "Testen Sie die SMTP-Verbindung",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token-URL",
"Token URL - Tooltip": "Token-URL",
"Type": "Typ",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Gemeinsam)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Kopiere den Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn-Anmeldeinformationen",
"input password": "Eingabe des Passworts"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content-Type",
"Content type - Tooltip": "Inhaltstyp",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Etiquetas a las que el usuario pertenece, con una configuración predeterminada en \"usuario-normal\"",
"Users": "Usuarios",
"Users under all organizations": "Usuarios bajo todas las organizaciones",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "vacío",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirigiendo, por favor espera.",
"Sign In": "Iniciar sesión",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Iniciar sesión con WebAuthn",
"Sign in with {type}": "Inicia sesión con {tipo}",
"Signing in...": "Iniciando sesión...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Dirección de correo electrónico para recibir mensajes de prueba",
"Test SMTP Connection": "Prueba de conexión SMTP",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "URL de token",
"Type": "Tipo",
@@ -888,7 +891,9 @@
"admin (Shared)": "administrador (compartido)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copiar enlace",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "Credenciales de WebAuthn",
"input password": "Ingresar contraseña"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Tipo de contenido",
"Content type - Tooltip": "Tipo de contenido",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Étiquettes associées au compte, avec une valeur par défaut \"normal-user\"",
"Users": "Comptes",
"Users under all organizations": "Comptes sous toutes les organisations",
"Verifications": "Verifications",
"Webhooks": "Crochets web",
"You can only select one physical group": "Vous ne pouvez sélectionner qu'un seul groupe physique",
"empty": "vide",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Veuillez entrer une organisation pour vous connecter",
"Redirecting, please wait.": "Redirection en cours, veuillez patienter.",
"Sign In": "Se connecter",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Connectez-vous avec WebAuthn",
"Sign in with {type}": "Connectez-vous avec {type}",
"Signing in...": "Connexion en cours...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Adresse e-mail pour recevoir des courriels de test",
"Test SMTP Connection": "Test de connexion SMTP",
"Third-party": "Tierce partie",
"This field is required": "This field is required",
"Token URL": "URL de jeton",
"Token URL - Tooltip": "URL de jeton",
"Type": "Type de texte",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Partagé)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copier le lien",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "Identifiants WebAuthn",
"input password": "saisir le mot de passe"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Type de contenu",
"Content type - Tooltip": "Type de contenu",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tag yang dimiliki oleh pengguna, defaultnya adalah \"normal-user\"",
"Users": "Pengguna-pengguna",
"Users under all organizations": "Pengguna di bawah semua organisasi",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "kosong",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Mengalihkan, harap tunggu.",
"Sign In": "Masuk",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Masuk dengan WebAuthn",
"Sign in with {type}": "Masuk dengan {type}",
"Signing in...": "Masuk...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Alamat email untuk menerima email percobaan",
"Test SMTP Connection": "Tes Koneksi SMTP",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL: Tautan Token",
"Token URL - Tooltip": "Token URL: URL Token",
"Type": "Jenis",
@@ -888,7 +891,9 @@
"admin (Shared)": "Admin (Berbagi)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Salin Tautan",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "Kredensial WebAuthn",
"input password": "masukkan kata sandi"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Jenis konten",
"Content type - Tooltip": "Tipe konten",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "ユーザーが属するタグは、デフォルトでは「通常ユーザー」となります",
"Users": "ユーザー",
"Users under all organizations": "すべての組織のユーザー",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "空",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "リダイレクト中、お待ちください。",
"Sign In": "サインイン",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "WebAuthnでサインインしてください",
"Sign in with {type}": "{type}でサインインしてください",
"Signing in...": "サインイン中...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "テストメールを受け取るためのメールアドレス",
"Test SMTP Connection": "SMTP接続をテストする",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "トークンのURL",
"Token URL - Tooltip": "トークンURL",
"Type": "タイプ",
@@ -888,7 +891,9 @@
"admin (Shared)": "管理者(共有)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "コピー リンク",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthnの資格情報",
"input password": "パスワードを入力してください"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "コンテンツタイプ",
"Content type - Tooltip": "コンテンツタイプ",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "사용자가 속한 태그는 기본적으로 \"보통 사용자\"로 설정됩니다",
"Users": "사용자",
"Users under all organizations": "모든 조직의 사용자",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "빈",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "리디렉팅 중입니다. 잠시 기다려주세요.",
"Sign In": "로그인",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "WebAuthn으로 로그인하세요",
"Sign in with {type}": "{type}로 로그인하세요",
"Signing in...": "로그인 중...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "테스트 메일을 받을 이메일 주소",
"Test SMTP Connection": "테스트 SMTP 연결",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "토큰 URL",
"Token URL - Tooltip": "토큰 URL",
"Type": "타입",
@@ -888,7 +891,9 @@
"admin (Shared)": "관리자 (공유)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "링크 복사하기",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "웹 인증 자격증명",
"input password": "비밀번호를 입력해주세요"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "콘텐츠 유형",
"Content type - Tooltip": "콘텐츠 유형",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags às quais o usuário pertence, com valor padrão de \"usuário-normal\"",
"Users": "Usuários",
"Users under all organizations": "Usuários em todas as organizações",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "vazio",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecionando, por favor aguarde.",
"Sign In": "Entrar",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Entrar com WebAuthn",
"Sign in with {type}": "Entrar com {type}",
"Signing in...": "Entrando...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Endereço de e-mail para receber e-mails de teste",
"Test SMTP Connection": "Testar Conexão SMTP",
"Third-party": "Terceiros",
"This field is required": "This field is required",
"Token URL": "URL do Token",
"Token URL - Tooltip": "URL do Token",
"Type": "Tipo",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Compartilhado)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copiar Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "Credenciais WebAuthn",
"input password": "Digite a senha"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Tipo de conteúdo",
"Content type - Tooltip": "Tipo de conteúdo",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Теги, к которым принадлежит пользователь, по умолчанию \"обычный пользователь\"",
"Users": "Пользователи",
"Users under all organizations": "Пользователи всех организаций",
"Verifications": "Verifications",
"Webhooks": "Вебхуки",
"You can only select one physical group": "You can only select one physical group",
"empty": "пустые",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Перенаправление, пожалуйста, подождите.",
"Sign In": "Войти",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Войти с помощью WebAuthn",
"Sign in with {type}": "Войти с помощью {type}",
"Signing in...": "Вход в систему...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Адрес электронной почты для получения тестовых писем",
"Test SMTP Connection": "Тестирование соединения SMTP",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Токен URL (URL-адрес маркера)",
"Token URL - Tooltip": "Токен URL",
"Type": "Тип",
@@ -888,7 +891,9 @@
"admin (Shared)": "администратор (общий)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Копировать ссылку",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn удостоверения",
"input password": "введите пароль"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Тип содержания",
"Content type - Tooltip": "Тип содержимого",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Kullanıcılar",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Yönlendiriliyor, lütfen bekleyiniz.",
"Sign In": "Oturum aç",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "{type} ile giriş yap",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "şifreyi girin"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Tags that the user belongs to, defaulting to \"normal-user\"",
"Users": "Users",
"Users under all organizations": "Users under all organizations",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "empty",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Email address to receive test mails",
"Test SMTP Connection": "Test SMTP Connection",
"Third-party": "Third-party",
"This field is required": "This field is required",
"Token URL": "Token URL",
"Token URL - Tooltip": "Token URL",
"Type": "Type",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin (Shared)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Copy Link",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn credentials",
"input password": "input password"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "Các thẻ mà người dùng thuộc vào, mặc định là \"người dùng bình thường\"",
"Users": "Người dùng",
"Users under all organizations": "Người dùng trong tất cả các tổ chức",
"Verifications": "Verifications",
"Webhooks": "Webhooks",
"You can only select one physical group": "You can only select one physical group",
"empty": "trống",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Đang chuyển hướng, vui lòng đợi.",
"Sign In": "Đăng nhập",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Đăng nhập với WebAuthn",
"Sign in with {type}": "Đăng nhập bằng {type}",
"Signing in...": "Đăng nhập...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "Địa chỉ email để nhận thư kiểm tra",
"Test SMTP Connection": "Kiểm tra kết nối SMTP",
"Third-party": "Bên thứ ba",
"This field is required": "This field is required",
"Token URL": "Đường dẫn mã thông báo",
"Token URL - Tooltip": "Địa chỉ của mã thông báo",
"Type": "Kiểu",
@@ -888,7 +891,9 @@
"admin (Shared)": "quản trị viên (Chung)"
},
"record": {
"Is triggered": "Is triggered"
"Is triggered": "Is triggered",
"Object": "Object",
"Response": "Response"
},
"resource": {
"Copy Link": "Sao chép liên kết",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "Chứng chỉ WebAuthn",
"input password": "Nhập mật khẩu"
},
"verification": {
"Receiver": "Receiver"
},
"webhook": {
"Content type": "Loại nội dung",
"Content type - Tooltip": "Loại nội dung",

View File

@@ -392,6 +392,7 @@
"User type - Tooltip": "用户所属的标签,默认为\"normal-user\"",
"Users": "用户",
"Users under all organizations": "所有组织里的用户",
"Verifications": "验证",
"Webhooks": "Webhooks",
"You can only select one physical group": "只能选择一个实体组",
"empty": "无",
@@ -494,6 +495,7 @@
"Please type an organization to sign in": "请输入要登录的组织",
"Redirecting, please wait.": "正在跳转, 请稍等.",
"Sign In": "登录",
"Sign in with Face ID": "人脸登录",
"Sign in with WebAuthn": "WebAuthn登录",
"Sign in with {type}": "{type}登录",
"Signing in...": "正在登录...",
@@ -869,6 +871,7 @@
"Test Email - Tooltip": "接收测试邮件的Email邮箱",
"Test SMTP Connection": "测试SMTP连接",
"Third-party": "第三方",
"This field is required": "此字段是必需的",
"Token URL": "Token URL",
"Token URL - Tooltip": "自定义OAuth的Token URL",
"Type": "类型",
@@ -888,7 +891,9 @@
"admin (Shared)": "admin共享"
},
"record": {
"Is triggered": "是否触发"
"Is triggered": "是否触发",
"Object": "实体",
"Response": "响应"
},
"resource": {
"Copy Link": "复制链接",
@@ -1159,6 +1164,9 @@
"WebAuthn credentials": "WebAuthn凭据",
"input password": "输入密码"
},
"verification": {
"Receiver": "接收者"
},
"webhook": {
"Content type": "内容类型",
"Content type - Tooltip": "内容类型",

View File

@@ -81,10 +81,12 @@ class SignupTable extends React.Component {
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
{name: "ID card", displayName: i18next.t("user:ID card")},
{name: "Email", displayName: i18next.t("general:Email")},
{name: "Password", displayName: i18next.t("general:Password")},
{name: "Confirm password", displayName: i18next.t("signup:Confirm")},
{name: "Email", displayName: i18next.t("general:Email")},
{name: "Phone", displayName: i18next.t("general:Phone")},
{name: "Email or Phone", displayName: i18next.t("general:Email or Phone")},
{name: "Phone or Email", displayName: i18next.t("general:Phone or Email")},
{name: "Invitation code", displayName: i18next.t("application:Invitation code")},
{name: "Agreement", displayName: i18next.t("signup:Agreement")},
{name: "Text 1", displayName: i18next.t("signup:Text 1")},

File diff suppressed because it is too large Load Diff