Compare commits

...

24 Commits

Author SHA1 Message Date
fe42b5e0ba feat: improve checkGroupName() (#3759) 2025-05-03 22:47:42 +08:00
383bf44391 feat: support OIDC device flow: "/api/device-auth" (#3757) 2025-04-30 23:42:26 +08:00
36f5de3203 feat: allow jwks to include the certs from non-admin owner (#3749) 2025-04-28 09:31:56 +08:00
eae69c41d7 feat: add object field filter for webhook (#3746) 2025-04-26 22:05:36 +08:00
91057f54f3 feat: add Pbkdf2DjangoCredManager (#3745) 2025-04-25 16:16:50 +08:00
daa7b79915 feat: improve error handling of webauthn login (#3744) 2025-04-24 01:11:24 +08:00
d3a5539dae feat: fix loading status not reset issue when failed to login (#3743) 2025-04-24 00:57:52 +08:00
7d1c614452 feat: use random name as name if user's name is invalid when created by third party provider (#3742) 2025-04-23 21:30:19 +08:00
e2eafa909b feat: fix MODEL_URL in FaceRecognitionModal 2025-04-21 09:10:30 +08:00
56bcef0592 feat: support application.formCss in forget-password page (#3733) 2025-04-19 22:59:21 +08:00
0860cbf343 feat: can specify content type and http body field mapping for Custom HTTP Email provider (#3730) 2025-04-17 01:59:11 +08:00
2f4180b1b6 feat: add missing currencies in plan edit page (#3727) 2025-04-15 16:01:14 +08:00
e3d5619b25 feat: support custom HTTP headers in custom HttpEmailProvider and hide unused fields (#3723) 2025-04-13 23:52:04 +08:00
019fd87b92 feat: fix code comment typos (#3724) 2025-04-13 17:57:37 +08:00
5c41c6c4a5 feat: add BRL currency 2025-04-11 22:24:45 +08:00
b7fafcc62b feat: improve InitFromFile() code order to fix GetOrganizationApplicationCount always returns 0 bug (#3720) 2025-04-11 01:43:54 +08:00
493ceddcd9 feat: improve error handling in system info page 2025-04-11 01:41:27 +08:00
fc618b9bd5 feat: add validation for optional fields in IntrospectionToken for custom token types (#3717) 2025-04-09 22:27:19 +08:00
a00900e405 feat: fix sqlite bug for failed to lookup Client-side Discoverable Credential: user not exist (#3719) 2025-04-09 22:26:47 +08:00
77ef5828dd feat(introspection): return correct active status for expired or revoked tokens (#3716) 2025-04-09 02:00:30 +08:00
c11f013e04 feat: return "Active: false" for expired token in IntrospectToken() (#3714) 2025-04-08 23:20:44 +08:00
b3bafe8402 feat: fix bug that unable to query webauthnCredentials when db is mssql or postgres in GetUserByWebauthID() (#3712) 2025-04-08 17:51:32 +08:00
f04a431d85 feat: Casdoor's LDAP client supports LDAP server's self-signed certificates now (#3709) 2025-04-07 02:02:32 +08:00
952538916d feat: check application existence in object.AddUser() (#3686) 2025-04-05 16:38:20 +08:00
69 changed files with 1087 additions and 105 deletions

View File

@ -47,6 +47,7 @@ p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, POST, /api/callback, *, *
p, *, *, POST, /api/device-auth, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, GET, /api/user, *, *

View File

@ -32,6 +32,7 @@ const (
ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
ResponseTypeDevice = "device"
)
type Response struct {

View File

@ -25,6 +25,7 @@ import (
"regexp"
"strconv"
"strings"
"time"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/conf"
@ -169,6 +170,32 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp.Data2 = user.NeedUpdatePassword
}
} else if form.Type == ResponseTypeDevice {
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
if !ok {
c.ResponseError(c.T("auth:UserCode Expired"))
return
}
authCacheCast := authCache.(object.DeviceAuthCache)
if authCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
c.ResponseError(c.T("auth:UserCode Expired"))
return
}
deviceAuthCacheDeviceCode, ok := object.DeviceAuthMap.Load(authCacheCast.UserName)
if !ok {
c.ResponseError(c.T("auth:DeviceCode Invalid"))
return
}
deviceAuthCacheDeviceCodeCast := deviceAuthCacheDeviceCode.(object.DeviceAuthCache)
deviceAuthCacheDeviceCodeCast.UserName = user.Name
deviceAuthCacheDeviceCodeCast.UserSignIn = true
object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast)
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword}
} else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil {
@ -242,6 +269,7 @@ func (c *ApiController) GetApplicationLogin() {
state := c.Input().Get("state")
id := c.Input().Get("id")
loginType := c.Input().Get("type")
userCode := c.Input().Get("userCode")
var application *object.Application
var msg string
@ -268,6 +296,19 @@ func (c *ApiController) GetApplicationLogin() {
c.ResponseError(err.Error())
return
}
} else if loginType == "device" {
deviceAuthCache, ok := object.DeviceAuthMap.Load(userCode)
if !ok {
c.ResponseError(c.T("auth:UserCode Invalid"))
return
}
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
application, err = object.GetApplication(deviceAuthCacheCast.ApplicationId)
if err != nil {
c.ResponseError(err.Error())
return
}
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
@ -1215,3 +1256,75 @@ func (c *ApiController) Callback() {
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
}
// DeviceAuth
// @Title DeviceAuth
// @Tag Device Authorization Endpoint
// @Description Endpoint for the device authorization flow
// @router /device-auth [post]
// @Success 200 {object} object.DeviceAuthResponse The Response object
func (c *ApiController) DeviceAuth() {
clientId := c.Input().Get("client_id")
scope := c.Input().Get("scope")
application, err := object.GetApplicationByClientId(clientId)
if err != nil {
c.Data["json"] = object.TokenError{
Error: err.Error(),
ErrorDescription: err.Error(),
}
c.ServeJSON()
return
}
if application == nil {
c.Data["json"] = object.TokenError{
Error: c.T("token:Invalid client_id"),
ErrorDescription: c.T("token:Invalid client_id"),
}
c.ServeJSON()
return
}
deviceCode := util.GenerateId()
userCode := util.GetRandomName()
generateTime := 0
for {
if generateTime > 5 {
c.Data["json"] = object.TokenError{
Error: "userCode gen",
ErrorDescription: c.T("token:Invalid client_id"),
}
c.ServeJSON()
return
}
_, ok := object.DeviceAuthMap.Load(userCode)
if !ok {
break
}
generateTime++
}
deviceAuthCache := object.DeviceAuthCache{
UserSignIn: false,
UserName: "",
Scope: scope,
ApplicationId: application.GetId(),
RequestAt: time.Now(),
}
userAuthCache := object.DeviceAuthCache{
UserSignIn: false,
UserName: deviceCode,
Scope: scope,
ApplicationId: application.GetId(),
RequestAt: time.Now(),
}
object.DeviceAuthMap.Store(deviceCode, deviceAuthCache)
object.DeviceAuthMap.Store(userCode, userAuthCache)
c.Data["json"] = object.GetDeviceAuthResponse(deviceCode, userCode, c.Ctx.Request.Host)
c.ServeJSON()
}

View File

@ -27,10 +27,10 @@ type LdapResp struct {
ExistUuids []string `json:"existUuids"`
}
//type LdapRespGroup struct {
// type LdapRespGroup struct {
// GroupId string
// GroupName string
//}
// }
type LdapSyncResp struct {
Exist []object.LdapUser `json:"exist"`
@ -61,18 +61,18 @@ func (c *ApiController) GetLdapUsers() {
}
defer conn.Close()
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
//if err != nil {
// groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
// if err != nil {
// c.ResponseError(err.Error())
// return
//}
// }
//for _, group := range groupsMap {
// for _, group := range groupsMap {
// resp.Groups = append(resp.Groups, LdapRespGroup{
// GroupId: group.GidNumber,
// GroupName: group.Cn,
// })
//}
// }
users, err := conn.GetLdapUsers(ldapServer)
if err != nil {
@ -269,7 +269,11 @@ func (c *ApiController) SyncLdapUsers() {
return
}
exist, failed, _ := object.SyncLdapUsers(owner, users, ldapId)
exist, failed, err := object.SyncLdapUsers(owner, users, ldapId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(&LdapSyncResp{
Exist: exist,

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"time"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -170,12 +171,17 @@ func (c *ApiController) GetOAuthToken() {
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
refreshToken := c.Input().Get("refresh_token")
deviceCode := c.Input().Get("device_code")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if len(c.Ctx.Input.RequestBody) != 0 {
if grantType == "urn:ietf:params:oauth:grant-type:device_code" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if len(c.Ctx.Input.RequestBody) != 0 && grantType != "urn:ietf:params:oauth:grant-type:device_code" {
// If clientId is empty, try to read data from RequestBody
var tokenRequest TokenRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
@ -219,6 +225,40 @@ func (c *ApiController) GetOAuthToken() {
}
}
if deviceCode != "" {
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
if !ok {
c.Data["json"] = object.TokenError{
Error: "expired_token",
ErrorDescription: "token is expired",
}
c.ServeJSON()
return
}
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
if !deviceAuthCacheCast.UserSignIn {
c.Data["json"] = object.TokenError{
Error: "authorization_pending",
ErrorDescription: "authorization pending",
}
c.ServeJSON()
return
}
if deviceAuthCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
c.Data["json"] = object.TokenError{
Error: "expired_token",
ErrorDescription: "token is expired",
}
c.ServeJSON()
return
}
object.DeviceAuthMap.Delete(deviceCode)
username = deviceAuthCacheCast.UserName
}
host := c.Ctx.Request.Host
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil {
@ -321,6 +361,11 @@ func (c *ApiController) IntrospectToken() {
return
}
respondWithInactiveToken := func() {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
}
tokenTypeHint := c.Input().Get("token_type_hint")
var token *object.Token
if tokenTypeHint != "" {
@ -329,7 +374,12 @@ func (c *ApiController) IntrospectToken() {
c.ResponseTokenError(err.Error())
return
}
if token == nil {
if token == nil || token.ExpiresIn <= 0 {
respondWithInactiveToken()
return
}
if token.ExpiresIn <= 0 {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
@ -344,8 +394,7 @@ func (c *ApiController) IntrospectToken() {
// and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
respondWithInactiveToken()
return
}
@ -369,24 +418,30 @@ func (c *ApiController) IntrospectToken() {
// and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
respondWithInactiveToken()
return
}
introspectionResponse = object.IntrospectionResponse{
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: jwtToken.Name,
TokenType: jwtToken.TokenType,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
Sub: jwtToken.Subject,
Aud: jwtToken.Audience,
Iss: jwtToken.Issuer,
Jti: jwtToken.ID,
Active: true,
ClientId: clientId,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
Sub: jwtToken.Subject,
Aud: jwtToken.Audience,
Iss: jwtToken.Issuer,
Jti: jwtToken.ID,
}
if jwtToken.Scope != "" {
introspectionResponse.Scope = jwtToken.Scope
}
if jwtToken.Name != "" {
introspectionResponse.Username = jwtToken.Name
}
if jwtToken.TokenType != "" {
introspectionResponse.TokenType = jwtToken.TokenType
}
}
@ -396,13 +451,15 @@ func (c *ApiController) IntrospectToken() {
c.ResponseTokenError(err.Error())
return
}
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
if token == nil || token.ExpiresIn <= 0 {
respondWithInactiveToken()
return
}
}
introspectionResponse.TokenType = token.TokenType
if token != nil {
introspectionResponse.TokenType = token.TokenType
}
c.Data["json"] = introspectionResponse
c.ServeJSON()

View File

@ -34,6 +34,8 @@ func GetCredManager(passwordType string) CredManager {
return NewPbkdf2SaltCredManager()
} else if passwordType == "argon2id" {
return NewArgon2idCredManager()
} else if passwordType == "pbkdf2-django" {
return NewPbkdf2DjangoCredManager()
}
return nil
}

71
cred/pbkdf2_django.go Normal file
View File

@ -0,0 +1,71 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cred
import (
"crypto/sha256"
"encoding/base64"
"strconv"
"strings"
"golang.org/x/crypto/pbkdf2"
)
// password type: pbkdf2-django
type Pbkdf2DjangoCredManager struct{}
func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
cm := &Pbkdf2DjangoCredManager{}
return cm
}
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
iterations := 260000
salt := userSalt
if salt == "" {
salt = organizationSalt
}
saltBytes := []byte(salt)
passwordBytes := []byte(password)
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iterations, sha256.Size, sha256.New)
hashBase64 := base64.StdEncoding.EncodeToString(computedHash)
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
}
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool {
parts := strings.Split(passwordHash, "$")
if len(parts) != 4 {
return false
}
algorithm, iterations, salt, hash := parts[0], parts[1], parts[2], parts[3]
if algorithm != "pbkdf2_sha256" {
return false
}
iter, err := strconv.Atoi(iterations)
if err != nil {
return false
}
saltBytes := []byte(salt)
passwordBytes := []byte(password)
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iter, sha256.Size, sha256.New)
computedHashBase64 := base64.StdEncoding.EncodeToString(computedHash)
return computedHashBase64 == hash
}

View File

@ -15,6 +15,8 @@
package email
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
@ -24,14 +26,24 @@ import (
)
type HttpEmailProvider struct {
endpoint string
method string
endpoint string
method string
httpHeaders map[string]string
bodyMapping map[string]string
contentType string
}
func NewHttpEmailProvider(endpoint string, method string) *HttpEmailProvider {
func NewHttpEmailProvider(endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) *HttpEmailProvider {
if contentType == "" {
contentType = "application/x-www-form-urlencoded"
}
client := &HttpEmailProvider{
endpoint: endpoint,
method: method,
endpoint: endpoint,
method: method,
httpHeaders: httpHeaders,
bodyMapping: bodyMapping,
contentType: contentType,
}
return client
}
@ -39,18 +51,52 @@ func NewHttpEmailProvider(endpoint string, method string) *HttpEmailProvider {
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
var req *http.Request
var err error
if c.method == "POST" {
formValues := url.Values{}
formValues.Set("fromName", fromName)
formValues.Set("toAddress", toAddress)
formValues.Set("subject", subject)
formValues.Set("content", content)
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
fromNameField := "fromName"
toAddressField := "toAddress"
subjectField := "subject"
contentField := "content"
for k, v := range c.bodyMapping {
switch k {
case "fromName":
fromNameField = v
case "toAddress":
toAddressField = v
case "subject":
subjectField = v
case "content":
contentField = v
}
}
if c.method == "POST" || c.method == "PUT" || c.method == "DELETE" {
bodyMap := make(map[string]string)
bodyMap[fromNameField] = fromName
bodyMap[toAddressField] = toAddress
bodyMap[subjectField] = subject
bodyMap[contentField] = content
var fromValueBytes []byte
if c.contentType == "application/json" {
fromValueBytes, err = json.Marshal(bodyMap)
if err != nil {
return err
}
req, err = http.NewRequest(c.method, c.endpoint, bytes.NewBuffer(fromValueBytes))
} else {
formValues := url.Values{}
for k, v := range bodyMap {
formValues.Add(k, v)
}
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
}
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Content-Type", c.contentType)
} else if c.method == "GET" {
req, err = http.NewRequest(c.method, c.endpoint, nil)
if err != nil {
@ -58,15 +104,19 @@ func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress
}
q := req.URL.Query()
q.Add("fromName", fromName)
q.Add("toAddress", toAddress)
q.Add("subject", subject)
q.Add("content", content)
q.Add(fromNameField, fromName)
q.Add(toAddressField, toAddress)
q.Add(subjectField, subject)
q.Add(contentField, content)
req.URL.RawQuery = q.Encode()
} else {
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
}
for k, v := range c.httpHeaders {
req.Header.Set(k, v)
}
httpClient := proxy.DefaultHttpClient
resp, err := httpClient.Do(req)
if err != nil {

View File

@ -18,11 +18,11 @@ type EmailProvider interface {
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
}
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string) EmailProvider {
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) EmailProvider {
if typ == "Azure ACS" {
return NewAzureACSEmailProvider(clientSecret, host)
} else if typ == "Custom HTTP Email" {
return NewHttpEmailProvider(endpoint, method)
return NewHttpEmailProvider(endpoint, method, httpHeaders, bodyMapping, contentType)
} else if typ == "SendGrid" {
return NewSendgridEmailProvider(clientSecret, host, endpoint)
} else {

View File

@ -70,6 +70,7 @@ type AuthForm struct {
FaceId []float64 `json:"faceId"`
FaceIdImage []string `json:"faceIdImage"`
UserCode string `json:"userCode"`
}
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {

View File

@ -136,12 +136,12 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
dtUserInfo := &DingTalkUserResponse{}
accessToken := token.AccessToken
reqest, err := http.NewRequest("GET", idp.Config.Endpoint.AuthURL, nil)
request, err := http.NewRequest("GET", idp.Config.Endpoint.AuthURL, nil)
if err != nil {
return nil, err
}
reqest.Header.Add("x-acs-dingtalk-access-token", accessToken)
resp, err := idp.Client.Do(reqest)
request.Header.Add("x-acs-dingtalk-access-token", accessToken)
resp, err := idp.Client.Do(request)
if err != nil {
return nil, err
}

View File

@ -299,12 +299,12 @@ func GetWechatOfficialAccountQRCode(clientId string, clientSecret string, provid
params := fmt.Sprintf(`{"expire_seconds": 3600, "action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": "%s"}}}`, providerId)
bodyData := bytes.NewReader([]byte(params))
requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData)
request, err := http.NewRequest("POST", qrCodeUrl, bodyData)
if err != nil {
return "", "", err
}
resp, err := client.Do(requeset)
resp, err := client.Do(request)
if err != nil {
return "", "", err
}

View File

@ -63,7 +63,11 @@ func GetCertCount(owner, field, value string) (int64, error) {
func GetCerts(owner string) ([]*Cert, error) {
certs := []*Cert{}
err := ormer.Engine.Where("owner = ? or owner = ? ", "admin", owner).Desc("created_time").Find(&certs, &Cert{})
db := ormer.Engine.NewSession()
if owner != "" {
db = db.Where("owner = ? or owner = ? ", "admin", owner)
}
err := db.Desc("created_time").Find(&certs, &Cert{})
if err != nil {
return certs, err
}

View File

@ -31,7 +31,7 @@ func TestSmtpServer(provider *Provider) error {
}
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method)
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method, provider.HttpHeaders, provider.UserMapping, provider.IssuerUrl)
fromAddress := provider.ClientId2
if fromAddress == "" {

View File

@ -17,6 +17,7 @@ package object
import (
"errors"
"fmt"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
@ -210,6 +211,12 @@ func DeleteGroup(group *Group) (bool, error) {
}
func checkGroupName(name string) error {
if name == "" {
return errors.New("group name can't be empty")
}
if strings.Contains(name, "/") {
return errors.New("group name can't contain \"/\"")
}
exist, err := ormer.Engine.Exist(&Organization{Owner: "admin", Name: name})
if err != nil {
return err

View File

@ -70,12 +70,12 @@ func InitFromFile() {
for _, provider := range initData.Providers {
initDefinedProvider(provider)
}
for _, user := range initData.Users {
initDefinedUser(user)
}
for _, application := range initData.Applications {
initDefinedApplication(application)
}
for _, user := range initData.Users {
initDefinedUser(user)
}
for _, cert := range initData.Certs {
initDefinedCert(cert)
}

View File

@ -23,17 +23,18 @@ type Ldap struct {
Owner string `xorm:"varchar(100)" json:"owner"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
ServerName string `xorm:"varchar(100)" json:"serverName"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `xorm:"int" json:"port"`
EnableSsl bool `xorm:"bool" json:"enableSsl"`
Username string `xorm:"varchar(100)" json:"username"`
Password string `xorm:"varchar(100)" json:"password"`
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
Filter string `xorm:"varchar(200)" json:"filter"`
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
ServerName string `xorm:"varchar(100)" json:"serverName"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `xorm:"int" json:"port"`
EnableSsl bool `xorm:"bool" json:"enableSsl"`
AllowSelfSignedCert bool `xorm:"bool" json:"allowSelfSignedCert"`
Username string `xorm:"varchar(100)" json:"username"`
Password string `xorm:"varchar(100)" json:"password"`
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
Filter string `xorm:"varchar(200)" json:"filter"`
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
AutoSync int `json:"autoSync"`
LastSync string `xorm:"varchar(100)" json:"lastSync"`
@ -150,7 +151,7 @@ func UpdateLdap(ldap *Ldap) (bool, error) {
}
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group", "password_type").Update(ldap)
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group", "password_type", "allow_self_signed_cert").Update(ldap)
if err != nil {
return false, nil
}

View File

@ -106,6 +106,12 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
}
existed, failed, err := SyncLdapUsers(ldap.Owner, AutoAdjustLdapUser(users), ldap.Id)
if err != nil {
conn.Close()
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
if len(failed) != 0 {
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(existed)-len(failed), len(failed)), failed)
logs.Warning(err.Error())

View File

@ -16,6 +16,7 @@ package object
import (
"crypto/md5"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
@ -64,8 +65,11 @@ type LdapUser struct {
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
var conn *goldap.Conn
tlsConfig := tls.Config{
InsecureSkipVerify: ldap.AllowSelfSignedCert,
}
if ldap.EnableSsl {
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), &tlsConfig)
} else {
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
}

View File

@ -30,6 +30,7 @@ type OidcDiscovery struct {
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
JwksUri string `json:"jwks_uri"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
ResponseTypesSupported []string `json:"response_types_supported"`
@ -119,6 +120,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
DeviceAuthorizationEndpoint: fmt.Sprintf("%s/api/device-auth", originBackend),
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
@ -138,7 +140,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
jwks := jose.JSONWebKeySet{}
certs, err := GetCerts("admin")
certs, err := GetCerts("")
if err != nil {
return jwks, err
}
@ -213,3 +215,14 @@ func GetWebFinger(resource string, rels []string, host string) (WebFinger, error
return wf, nil
}
func GetDeviceAuthResponse(deviceCode string, userCode string, host string) DeviceAuthResponse {
originFrontend, _ := getOriginFromHost(host)
return DeviceAuthResponse{
DeviceCode: deviceCode,
UserCode: userCode,
VerificationUri: fmt.Sprintf("%s/login/oauth/device/%s", originFrontend, userCode),
ExpiresIn: 120,
}
}

View File

@ -157,7 +157,7 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) (*Ormer
return a, nil
}
// NewAdapterFromdb is the constructor for Ormer.
// NewAdapterFromDb is the constructor for Ormer.
func NewAdapterFromDb(driverName string, dataSourceName string, dbName string, db *sql.DB) (*Ormer, error) {
a := &Ormer{}
a.driverName = driverName

View File

@ -48,6 +48,7 @@ type Provider struct {
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
Scopes string `xorm:"varchar(100)" json:"scopes"`
UserMapping map[string]string `xorm:"varchar(500)" json:"userMapping"`
HttpHeaders map[string]string `xorm:"varchar(500)" json:"httpHeaders"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`

View File

@ -263,6 +263,27 @@ func addWebhookRecord(webhook *Webhook, record *casvisorsdk.Record, statusCode i
return err
}
func filterRecordObject(object string, objectFields []string) string {
var rawObject map[string]interface{}
_ = json.Unmarshal([]byte(object), &rawObject)
if rawObject == nil {
return object
}
filteredObject := make(map[string]interface{})
for _, field := range objectFields {
fieldValue, ok := rawObject[field]
if !ok {
continue
}
filteredObject[field] = fieldValue
}
return util.StructToJson(filteredObject)
}
func SendWebhooks(record *casvisorsdk.Record) error {
webhooks, err := getWebhooksByOrganization("")
if err != nil {
@ -271,7 +292,14 @@ func SendWebhooks(record *casvisorsdk.Record) error {
errs := []error{}
webhooks = getFilteredWebhooks(webhooks, record.Organization, record.Action)
record2 := *record
for _, webhook := range webhooks {
if len(webhook.ObjectFields) != 0 && webhook.ObjectFields[0] != "All" {
record2.Object = filterRecordObject(record.Object, webhook.ObjectFields)
}
var user *User
if webhook.IsUserExtended {
user, err = getUser(record.Organization, record.User)
@ -287,12 +315,12 @@ func SendWebhooks(record *casvisorsdk.Record) error {
}
}
statusCode, respBody, err := sendWebhook(webhook, record, user)
statusCode, respBody, err := sendWebhook(webhook, &record2, user)
if err != nil {
errs = append(errs, err)
}
err = addWebhookRecord(webhook, record, statusCode, respBody, err)
err = addWebhookRecord(webhook, &record2, statusCode, respBody, err)
if err != nil {
errs = append(errs, err)
}

View File

@ -18,6 +18,7 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
"sync"
"time"
"github.com/casdoor/casdoor/i18n"
@ -37,6 +38,8 @@ const (
EndpointError = "endpoint_error"
)
var DeviceAuthMap = sync.Map{}
type Code struct {
Message string `xorm:"varchar(100)" json:"message"`
Code string `xorm:"varchar(100)" json:"code"`
@ -71,6 +74,22 @@ type IntrospectionResponse struct {
Jti string `json:"jti,omitempty"`
}
type DeviceAuthCache struct {
UserSignIn bool
UserName string
ApplicationId string
Scope string
RequestAt time.Time
}
type DeviceAuthResponse struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationUri string `json:"verification_uri"`
ExpiresIn int `json:"expires_in"`
Interval int `json:"interval"`
}
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
token, err := GetTokenByAccessToken(accessToken)
if err != nil {
@ -222,6 +241,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
case "token", "id_token": // Implicit Grant
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
case "urn:ietf:params:oauth:grant-type:device_code":
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
case "refresh_token":
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil {

View File

@ -461,7 +461,18 @@ func GetUserByEmail(owner string, email string) (*User, error) {
func GetUserByWebauthID(webauthId string) (*User, error) {
user := User{}
existed, err := ormer.Engine.Where("webauthnCredentials LIKE ?", "%"+webauthId+"%").Get(&user)
existed := false
var err error
if ormer.driverName == "postgres" {
existed, err = ormer.Engine.Where(builder.Like{"\"webauthnCredentials\"", webauthId}).Get(&user)
} else if ormer.driverName == "mssql" {
existed, err = ormer.Engine.Where("CAST(webauthnCredentials AS VARCHAR(MAX)) like ?", "%"+webauthId+"%").Get(&user)
} else if ormer.driverName == "sqlite" {
existed, err = ormer.Engine.Where("CAST(webauthnCredentials AS text) like ?", "%"+webauthId+"%").Get(&user)
} else {
existed, err = ormer.Engine.Where("webauthnCredentials like ?", "%"+webauthId+"%").Get(&user)
}
if err != nil {
return nil, err
}
@ -826,6 +837,10 @@ func AddUser(user *User) (bool, error) {
return false, fmt.Errorf("the user's owner and name should not be empty")
}
if CheckUsername(user.Name, "en") != "" {
user.Name = util.GetRandomName()
}
organization, err := GetOrganizationByUser(user)
if err != nil {
return false, err
@ -834,6 +849,16 @@ func AddUser(user *User) (bool, error) {
return false, fmt.Errorf("the organization: %s is not found", user.Owner)
}
if user.Owner != "built-in" {
applicationCount, err := GetOrganizationApplicationCount(organization.Owner, organization.Name, "", "")
if err != nil {
return false, err
}
if applicationCount == 0 {
return false, fmt.Errorf("The organization: %s should have one application at least", organization.Owner)
}
}
if organization.DefaultPassword != "" && user.Password == "123" {
user.Password = organization.DefaultPassword
}

View File

@ -39,6 +39,7 @@ type Webhook struct {
Headers []*Header `xorm:"mediumtext" json:"headers"`
Events []string `xorm:"varchar(1000)" json:"events"`
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
ObjectFields []string `xorm:"varchar(1000)" json:"objectFields"`
IsUserExtended bool `json:"isUserExtended"`
SingleOrgOnly bool `json:"singleOrgOnly"`
IsEnabled bool `json:"isEnabled"`

View File

@ -66,6 +66,7 @@ func initAPI() {
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
beego.Router("/api/device-auth", &controllers.ApiController{}, "POST:DeviceAuth")
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")

View File

@ -53,6 +53,14 @@ const template = `<style>
background-color: #333333;
box-shadow: 0 0 30px 20px rgba(255, 255, 255, 0.20);
}
.forget-content {
padding: 10px 100px 20px;
margin: 30px auto;
border: 2px solid #fff;
border-radius: 7px;
background-color: rgb(255 255 255);
box-shadow: 0 0 20px rgb(0 0 0 / 20%);
}
</style>`;
const previewGrid = Setting.isMobile() ? 22 : 11;
@ -718,6 +726,7 @@ class ApplicationEditPage extends React.Component {
{id: "token", name: "Token"},
{id: "id_token", name: "ID Token"},
{id: "refresh_token", name: "Refresh Token"},
{id: "urn:ietf:params:oauth:grant-type:device_code", name: "Device Code"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>

View File

@ -119,6 +119,7 @@ class EntryPage extends React.Component {
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/oauth/device/:userCode" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"device"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/forget" render={(props) => <SelfForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/forget/:applicationName" render={(props) => <ForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />

View File

@ -170,6 +170,16 @@ class LdapEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
{Setting.getLabel(i18next.t("ldap:Allow self-signed certificate"), i18next.t("ldap:Allow self-signed certificate - Tooltip"))} :
</Col>
<Col span={21} >
<Switch checked={this.state.ldap.allowSelfSignedCert} onChange={checked => {
this.updateLdapField("allowSelfSignedCert", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}}>
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
{Setting.getLabel(i18next.t("ldap:Base DN"), i18next.t("ldap:Base DN - Tooltip"))} :

View File

@ -276,7 +276,7 @@ class OrganizationEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id"].map(item => Setting.getOption(item, item))}
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id", "pbkdf2-django"].map(item => Setting.getOption(item, item))}
/>
</Col>
</Row>

View File

@ -232,6 +232,15 @@ class PlanEditPage extends React.Component {
[
{id: "USD", name: "USD"},
{id: "CNY", name: "CNY"},
{id: "EUR", name: "EUR"},
{id: "JPY", name: "JPY"},
{id: "GBP", name: "GBP"},
{id: "AUD", name: "AUD"},
{id: "CAD", name: "CAD"},
{id: "CHF", name: "CHF"},
{id: "HKD", name: "HKD"},
{id: "SGD", name: "SGD"},
{id: "BRL", name: "BRL"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>

View File

@ -139,6 +139,8 @@ class ProductBuyPage extends React.Component {
return "HK$";
} else if (product?.currency === "SGD") {
return "S$";
} else if (product?.currency === "BRL") {
return "R$";
} else {
return "(Unknown currency)";
}

View File

@ -217,6 +217,7 @@ class ProductEditPage extends React.Component {
{id: "CHF", name: "CHF"},
{id: "HKD", name: "HKD"},
{id: "SGD", name: "SGD"},
{id: "BRL", name: "BRL"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>

View File

@ -29,6 +29,7 @@ import {CaptchaPreview} from "./common/CaptchaPreview";
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
import * as Web3Auth from "./auth/Web3Auth";
import Editor from "./common/Editor";
import HttpHeaderTable from "./table/HttpHeaderTable";
const {Option} = Select;
const {TextArea} = Input;
@ -41,6 +42,13 @@ const defaultUserMapping = {
avatarUrl: "avatarUrl",
};
const defaultEmailMapping = {
fromName: "fromName",
toAddress: "toAddress",
subject: "subject",
content: "content",
};
class ProviderEditPage extends React.Component {
constructor(props) {
super(props);
@ -71,7 +79,16 @@ class ProviderEditPage extends React.Component {
if (res.status === "ok") {
const provider = res.data;
provider.userMapping = provider.userMapping || defaultUserMapping;
if (provider.type === "Custom HTTP Email") {
if (!provider.userMapping) {
provider.userMapping = provider.userMapping || defaultEmailMapping;
}
if (!provider.userMapping?.fromName) {
provider.userMapping = defaultEmailMapping;
}
} else {
provider.userMapping = provider.userMapping || defaultUserMapping;
}
this.setState({
provider: provider,
});
@ -145,9 +162,16 @@ class ProviderEditPage extends React.Component {
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;
if (provider.type === "Custom HTTP Email") {
if (value === "") {
Setting.showMessage("error", i18next.t("provider:This field is required"));
return;
}
} else {
if (value === "" && requiredKeys.includes(key)) {
Setting.showMessage("error", i18next.t("provider:This field is required"));
return;
}
}
provider.userMapping[key] = value;
@ -183,6 +207,30 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
);
}
renderEmailMappingInput() {
return (
<React.Fragment>
{Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip"))} :
<Input value={this.state.provider.userMapping.fromName} onChange={e => {
this.updateUserMappingField("fromName", e.target.value);
}} />
{Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip"))} :
<Input value={this.state.provider.userMapping.toAddress} onChange={e => {
this.updateUserMappingField("toAddress", e.target.value);
}} />
{Setting.getLabel(i18next.t("provider:Subject"), i18next.t("provider:Subject - Tooltip"))} :
<Input value={this.state.provider.userMapping.subject} onChange={e => {
this.updateUserMappingField("subject", e.target.value);
}} />
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
<Input value={this.state.provider.userMapping.content} onChange={e => {
this.updateUserMappingField("content", e.target.value);
}} />
</React.Fragment>
);
}
getClientIdLabel(provider) {
switch (provider.category) {
case "OAuth":
@ -771,6 +819,7 @@ class ProviderEditPage extends React.Component {
(this.state.provider.category === "Web3") ||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
(this.state.provider.category === "Email" && this.state.provider.type === "Custom HTTP Email") ||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP") || this.state.provider.type === "Balance") ? null : (
<React.Fragment>
{
@ -916,7 +965,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "SendGrid", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Custom HTTP Email", "SendGrid", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
@ -928,7 +977,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "SendGrid", "Local File System", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Custom HTTP Email", "SendGrid", "Local File System", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ?
@ -942,7 +991,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "SendGrid", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Custom HTTP Email", "SendGrid", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
@ -954,7 +1003,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "SendGrid", "Synology", "Casdoor", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Custom HTTP Email", "SendGrid", "Synology", "Casdoor", "CUCloud", "Alibaba Cloud Facebody"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@ -1095,6 +1144,66 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{
!["Custom HTTP Email"].includes(this.state.provider.type) ? null : (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.method} onChange={value => {
this.updateProviderField("method", value);
}}>
{
[
{id: "GET", name: "GET"},
{id: "POST", name: "POST"},
{id: "PUT", name: "PUT"},
{id: "DELETE", name: "DELETE"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>
{
this.state.provider.method !== "GET" ? (<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.issuerUrl === "" ? "application/x-www-form-urlencoded" : this.state.provider.issuerUrl} onChange={value => {
this.updateProviderField("issuerUrl", value);
}}>
{
[
{id: "application/json", name: "application/json"},
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
</Col>
<Col span={22} >
<HttpHeaderTable httpHeaders={this.state.provider.httpHeaders} onUpdateTable={(value) => {this.updateProviderField("httpHeaders", value);}} />
</Col>
</Row>
{this.state.provider.method !== "GET" ? <Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:HTTP body mapping"), i18next.t("provider:HTTP body mapping - Tooltip"))} :
</Col>
<Col span={22}>
{this.renderEmailMappingInput()}
</Col>
</Row> : null}
</React.Fragment>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email title"), i18next.t("provider:Email title - Tooltip"))} :
@ -1163,7 +1272,7 @@ class ProviderEditPage extends React.Component {
</Button>
</Row>
</React.Fragment>
) : this.state.provider.category === "SMS" ? (
) : ["SMS"].includes(this.state.provider.category) ? (
<React.Fragment>
{["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
null :

View File

@ -1603,6 +1603,8 @@ export function getCurrencyText(product) {
return i18next.t("currency:HKD");
} else if (product?.currency === "SGD") {
return i18next.t("currency:SGD");
} else if (product?.currency === "BRL") {
return i18next.t("currency:BRL");
} else {
return "(Unknown currency)";
}

View File

@ -37,17 +37,35 @@ class SystemInfo extends React.Component {
UNSAFE_componentWillMount() {
SystemBackend.getSystemInfo("").then(res => {
this.setState({
systemInfo: res.data,
loading: false,
});
if (res.status === "ok") {
this.setState({
systemInfo: res.data,
});
} else {
Setting.showMessage("error", res.msg);
this.stopTimer();
}
const id = setInterval(() => {
SystemBackend.getSystemInfo("").then(res => {
this.setState({
systemInfo: res.data,
loading: false,
});
if (res.status === "ok") {
this.setState({
systemInfo: res.data,
});
} else {
Setting.showMessage("error", res.msg);
this.stopTimer();
}
}).catch(error => {
Setting.showMessage("error", `System info failed to get: ${error}`);
this.stopTimer();
});
SystemBackend.getPrometheusInfo().then(res => {
this.setState({
@ -55,17 +73,25 @@ class SystemInfo extends React.Component {
});
});
}, 1000 * 2);
this.setState({intervalId: id});
}).catch(error => {
Setting.showMessage("error", `System info failed to get: ${error}`);
this.stopTimer();
});
SystemBackend.getVersionInfo().then(res => {
this.setState({
versionInfo: res.data,
});
if (res.status === "ok") {
this.setState({
versionInfo: res.data,
});
} else {
Setting.showMessage("error", res.msg);
this.stopTimer();
}
}).catch(err => {
Setting.showMessage("error", `Version info failed to get: ${err}`);
this.stopTimer();
});
}
@ -77,10 +103,14 @@ class SystemInfo extends React.Component {
this.setState({isTourVisible: TourConfig.getTourVisible()});
};
componentWillUnmount() {
stopTimer() {
if (this.state.intervalId !== null) {
clearInterval(this.state.intervalId);
}
}
componentWillUnmount() {
this.stopTimer();
window.removeEventListener("storageTourChanged", this.handleTourChange);
}
@ -125,9 +155,9 @@ class SystemInfo extends React.Component {
<br /> <br />
<Progress type="circle" percent={Number((Number(this.state.systemInfo.memoryUsed) / Number(this.state.systemInfo.memoryTotal) * 100).toFixed(2))} />
</div>;
const latencyUi = this.state.prometheusInfo.apiLatency === null || this.state.prometheusInfo.apiLatency?.length <= 0 ? <Spin size="large" /> :
const latencyUi = this.state.prometheusInfo?.apiLatency === null || this.state.prometheusInfo?.apiLatency?.length <= 0 ? <Spin size="large" /> :
<PrometheusInfoTable prometheusInfo={this.state.prometheusInfo} table={"latency"} />;
const throughputUi = this.state.prometheusInfo.apiThroughput === null || this.state.prometheusInfo.apiThroughput?.length <= 0 ? <Spin size="large" /> :
const throughputUi = this.state.prometheusInfo?.apiThroughput === null || this.state.prometheusInfo?.apiThroughput?.length <= 0 ? <Spin size="large" /> :
<PrometheusInfoTable prometheusInfo={this.state.prometheusInfo} table={"throughput"} />;
const link = this.state.versionInfo?.version !== "" ? `https://github.com/casdoor/casdoor/releases/tag/${this.state.versionInfo?.version}` : "";
let versionText = this.state.versionInfo?.version !== "" ? this.state.versionInfo?.version : i18next.t("system:Unknown version");

View File

@ -144,6 +144,9 @@ class WebhookEditPage extends React.Component {
if (["port"].includes(key)) {
value = Setting.myParseInt(value);
}
if (key === "objectFields") {
value = value.includes("All") ? ["All"] : value;
}
return value;
}
@ -294,6 +297,19 @@ class WebhookEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Object fields"), i18next.t("webhook:Object fields - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" showSearch style={{width: "100%"}} value={this.state.webhook.objectFields} onChange={(value => {this.updateWebhookField("objectFields", value);})}>
<Option key="All" value="All">{i18next.t("general:All")}</Option>
{
["owner", "name", "createdTime", "updatedTime", "deletedTime", "id", "displayName"].map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :

View File

@ -61,7 +61,14 @@ export function oAuthParamsToQuery(oAuthParams) {
}
export function getApplicationLogin(params) {
const queryParams = (params?.type === "cas") ? casLoginParamsToQuery(params) : oAuthParamsToQuery(params);
let queryParams = "";
if (params?.type === "cas") {
queryParams = casLoginParamsToQuery(params);
} else if (params?.type === "device") {
queryParams = `?userCode=${params.userCode}&type=device`;
} else {
queryParams = oAuthParamsToQuery(params);
}
return fetch(`${authConfig.serverUrl}/api/get-app-login${queryParams}`, {
method: "GET",
credentials: "include",

View File

@ -471,6 +471,8 @@ class ForgetPage extends React.Component {
<React.Fragment>
<CustomGithubCorner />
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
{Setting.inIframe() || !Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCssMobile}} />}
<Button type="text"
style={{position: "relative", left: Setting.isMobile() ? "10px" : "-90px", top: 0}}
icon={<ArrowLeftOutlined style={{fontSize: "24px"}} />}

View File

@ -65,6 +65,8 @@ class LoginPage extends React.Component {
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
userLang: null,
loginLoading: false,
userCode: props.userCode ?? (props.match?.params?.userCode ?? null),
userCodeStatus: "",
};
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
@ -81,7 +83,7 @@ class LoginPage extends React.Component {
if (this.getApplicationObj() === undefined) {
if (this.state.type === "login" || this.state.type === "saml") {
this.getApplication();
} else if (this.state.type === "code" || this.state.type === "cas") {
} else if (this.state.type === "code" || this.state.type === "cas" || this.state.type === "device") {
this.getApplicationLogin();
} else {
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
@ -155,13 +157,25 @@ class LoginPage extends React.Component {
}
getApplicationLogin() {
const loginParams = (this.state.type === "cas") ? Util.getCasLoginParameters("admin", this.state.applicationName) : Util.getOAuthGetParameters();
let loginParams;
if (this.state.type === "cas") {
loginParams = Util.getCasLoginParameters("admin", this.state.applicationName);
} else if (this.state.type === "device") {
loginParams = {userCode: this.state.userCode, type: this.state.type};
} else {
loginParams = Util.getOAuthGetParameters();
}
AuthBackend.getApplicationLogin(loginParams)
.then((res) => {
if (res.status === "ok") {
const application = res.data;
this.onUpdateApplication(application);
} else {
if (this.state.type === "device") {
this.setState({
userCodeStatus: "expired",
});
}
this.onUpdateApplication(null);
this.setState({
msg: res.msg,
@ -266,6 +280,9 @@ class LoginPage extends React.Component {
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
if (application === null) {
return;
}
for (const idx in application.providers) {
const provider = application.providers[idx];
if (provider.provider?.category === "Face ID") {
@ -296,6 +313,9 @@ class LoginPage extends React.Component {
const oAuthParams = Util.getOAuthGetParameters();
values["type"] = oAuthParams?.responseType ?? this.state.type;
if (this.state.userCode) {
values["userCode"] = this.state.userCode;
}
if (oAuthParams?.samlRequest) {
values["samlRequest"] = oAuthParams.samlRequest;
@ -391,6 +411,9 @@ class LoginPage extends React.Component {
}).then(res => res.json())
.then((res) => {
if (res.status === "error") {
this.setState({
loginLoading: false,
});
Setting.showMessage("error", res.msg);
return;
}
@ -455,6 +478,7 @@ class LoginPage extends React.Component {
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
}
}).finally(() => {
this.setState({loginLoading: false});
});
} else {
@ -475,6 +499,11 @@ class LoginPage extends React.Component {
this.props.onLoginSuccess();
} else if (responseType === "code") {
this.postCodeLoginAction(res);
} else if (responseType === "device") {
Setting.showMessage("success", "Successful login");
this.setState({
userCodeStatus: "success",
});
} else if (responseType === "token" || responseType === "id_token") {
if (res.data2) {
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
@ -511,6 +540,7 @@ class LoginPage extends React.Component {
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
}
}).finally(() => {
this.setState({loginLoading: false});
});
}
@ -720,7 +750,7 @@ class LoginPage extends React.Component {
values["FaceIdImage"] = FaceIdImage;
this.login(values);
this.setState({openFaceRecognitionModal: false});
}} onCancel={() => this.setState({openFaceRecognitionModal: false})} /></Suspense> :
}} onCancel={() => this.setState({openFaceRecognitionModal: false, loginLoading: false})} /></Suspense> :
<Suspense fallback={null}>
<FaceRecognitionModal
visible={this.state.openFaceRecognitionModal}
@ -731,7 +761,7 @@ class LoginPage extends React.Component {
this.login(values);
this.setState({openFaceRecognitionModal: false});
}}
onCancel={() => this.setState({openFaceRecognitionModal: false})}
onCancel={() => this.setState({openFaceRecognitionModal: false, loginLoading: false})}
/>
</Suspense>
:
@ -821,6 +851,16 @@ class LoginPage extends React.Component {
);
}
if (this.state.userCode && this.state.userCodeStatus === "success") {
return (
<Result
status="success"
title={i18next.t("application:Logged in successfully")}
>
</Result>
);
}
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
if (showForm) {
let loginWidth = 320;
@ -981,6 +1021,10 @@ class LoginPage extends React.Component {
return null;
}
if (this.state.userCode && this.state.userCodeStatus === "success") {
return null;
}
return (
<div>
<div style={{fontSize: 16, textAlign: "left"}}>
@ -1065,6 +1109,12 @@ class LoginPage extends React.Component {
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}${error}`);
});
}).catch(error => {
Setting.showMessage("error", `${error}`);
}).finally(() => {
this.setState({
loginLoading: false,
});
});
}
@ -1257,6 +1307,15 @@ class LoginPage extends React.Component {
}
render() {
if (this.state.userCodeStatus === "expired") {
return <Result
style={{width: "100%"}}
status="error"
title={`Code ${i18next.t("subscription:Expired")}`}
>
</Result>;
}
const application = this.getApplicationObj();
if (application === undefined) {
return null;

View File

@ -17,6 +17,7 @@ import React, {useState} from "react";
import {Button, Modal, Progress, Space, Spin, message} from "antd";
import i18next from "i18next";
import Dragger from "antd/es/upload/Dragger";
import * as Setting from "../../Setting";
const FaceRecognitionModal = (props) => {
const {visible, onOk, onCancel, withImage} = props;
@ -35,9 +36,8 @@ const FaceRecognitionModal = (props) => {
React.useEffect(() => {
const loadModels = async() => {
// const MODEL_URL = process.env.PUBLIC_URL + "/models";
// const MODEL_URL = "https://justadudewhohacks.github.io/face-api.js/models";
const MODEL_URL = "https://cdn.casdoor.com/casdoor/models";
const MODEL_URL = `${Setting.StaticBaseUrl}/casdoor/models`;
Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "HTML patičky",
"Footer HTML - Edit": "Upravit HTML patičky",
"Footer HTML - Tooltip": "Přizpůsobit patičku vaší aplikace",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Pozice formuláře",
"Form position - Tooltip": "Umístění formulářů pro registraci, přihlášení a zapomenuté heslo",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "URL ikony favicon použité na všech stránkách Casdoor organizace",
"First name": "Křestní jméno",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "URL pro zapomenutí",
"Forget URL - Tooltip": "Vlastní URL pro stránku \"Zapomenuté heslo\". Pokud není nastaveno, bude použita výchozí stránka Casdoor \"Zapomenuté heslo\". Když je nastaveno, odkaz \"Zapomenuté heslo\" na přihlašovací stránce přesměruje na tuto URL",
"Found some texts still not translated? Please help us translate at": "Našli jste nějaké texty, které ještě nejsou přeloženy? Pomozte nám prosím přeložit na",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN nebo ID administrátora LDAP serveru",
"Admin Password": "Heslo administrátora",
"Admin Password - Tooltip": "Heslo administrátora LDAP serveru",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Automatická synchronizace",
"Auto Sync - Tooltip": "Konfigurace automatické synchronizace, deaktivováno při 0",
"Base DN": "Základní DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Jméno \"Z\"",
"Get phone number": "Získat telefonní číslo",
"Get phone number - Tooltip": "Pokud je povolena synchronizace telefonního čísla, měli byste nejprve povolit google people API a přidat rozsah https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Hostitel",
"Host - Tooltip": "Název hostitele",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Formposition",
"Form position - Tooltip": "Position der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon-URL, die auf allen Casdoor-Seiten der Organisation verwendet wird",
"First name": "Vorname",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Passwort vergessen URL",
"Forget URL - Tooltip": "Benutzerdefinierte URL für die \"Passwort vergessen\" Seite. Wenn nicht festgelegt, wird die standardmäßige Casdoor \"Passwort vergessen\" Seite verwendet. Wenn sie festgelegt ist, wird der \"Passwort vergessen\" Link auf der Login-Seite zu dieser URL umgeleitet",
"Found some texts still not translated? Please help us translate at": "Haben Sie noch Texte gefunden, die nicht übersetzt wurden? Bitte helfen Sie uns beim Übersetzen",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN oder ID des LDAP-Serveradministrators",
"Admin Password": "Administratoren-Passwort",
"Admin Password - Tooltip": "LDAP-Server-Administratorpasswort",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto-Synchronisierung",
"Auto Sync - Tooltip": "Auto-Sync-Konfiguration, deaktiviert um 0 Uhr",
"Base DN": "Basis-DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name des Hosts",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Posición de la Forma",
"Form position - Tooltip": "Ubicación de los formularios de registro, inicio de sesión y olvido de contraseña",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon (ícono de favoritos)",
"Favicon - Tooltip": "URL del icono Favicon utilizado en todas las páginas de Casdoor de la organización",
"First name": "Nombre de pila",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Olvide la URL",
"Forget URL - Tooltip": "URL personalizada para la página \"Olvidé mi contraseña\". Si no se establece, se utilizará la página \"Olvidé mi contraseña\" predeterminada de Casdoor. Cuando se establezca, el enlace \"Olvidé mi contraseña\" en la página de inicio de sesión redireccionará a esta URL",
"Found some texts still not translated? Please help us translate at": "¿Encontraste algunos textos que aún no están traducidos? Por favor, ayúdanos a traducirlos en",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN o ID del administrador del servidor LDAP",
"Admin Password": "Contraseña de administrador",
"Admin Password - Tooltip": "Contraseña del administrador del servidor LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Sincronización automática",
"Auto Sync - Tooltip": "Configuración de sincronización automática, desactivada a las 0",
"Base DN": "DN base",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Anfitrión",
"Host - Tooltip": "Nombre del anfitrión",
"IdP": "IdP = Proveedor de Identidad",

View File

@ -65,6 +65,7 @@
"Footer HTML": "HTML پاورقی",
"Footer HTML - Edit": "ویرایش HTML پاورقی",
"Footer HTML - Tooltip": "پاورقی برنامه خود را سفارشی کنید",
"Forced redirect origin": "Forced redirect origin",
"Form position": "موقعیت فرم",
"Form position - Tooltip": "مکان فرم‌های ثبت‌نام، ورود و فراموشی رمز عبور",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "آدرس آیکون Favicon استفاده شده در تمام صفحات Casdoor سازمان",
"First name": "نام",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "آدرس فراموشی",
"Forget URL - Tooltip": "آدرس سفارشی برای صفحه \"فراموشی رمز عبور\". اگر تنظیم نشده باشد، صفحه پیش‌فرض \"فراموشی رمز عبور\" Casdoor استفاده می‌شود. هنگامی که تنظیم شده باشد، لینک \"فراموشی رمز عبور\" در صفحه ورود به این آدرس هدایت می‌شود",
"Found some texts still not translated? Please help us translate at": "برخی متون هنوز ترجمه نشده‌اند؟ لطفاً به ما در ترجمه کمک کنید در",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN یا ID مدیر سرور LDAP",
"Admin Password": "رمز عبور مدیر",
"Admin Password - Tooltip": "رمز عبور مدیر سرور LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "همگام‌سازی خودکار",
"Auto Sync - Tooltip": "پیکربندی همگام‌سازی خودکار، در ۰ غیرفعال است",
"Base DN": "پایه DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "نام \"از\"",
"Get phone number": "دریافت شماره تلفن",
"Get phone number - Tooltip": "اگر همگام‌سازی شماره تلفن فعال باشد، باید ابتدا API افراد گوگل را فعال کنید و محدوده https://www.googleapis.com/auth/user.phonenumbers.read را اضافه کنید",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "میزبان",
"Host - Tooltip": "نام میزبان",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Position du formulaire",
"Form position - Tooltip": "Emplacement des formulaires d'inscription, de connexion et de récupération de mot de passe",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Icône du site",
"Favicon - Tooltip": "L'URL de l'icône « favicon » utilisée dans toutes les pages Casdoor de l'organisation",
"First name": "Prénom",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "URL d'oubli",
"Forget URL - Tooltip": "URL personnalisée pour la page \"Mot de passe oublié\". Si elle n'est pas définie, la page par défaut \"Mot de passe oublié\" de Casdoor sera utilisée. Lorsqu'elle est définie, le lien \"Mot de passe oublié\" sur la page de connexion sera redirigé vers cette URL",
"Found some texts still not translated? Please help us translate at": "Trouvé des textes encore non traduits ? Veuillez nous aider à les traduire sur",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN ou ID du compte d'administration du serveur LDAP",
"Admin Password": "Mot de passe du compte d'administration",
"Admin Password - Tooltip": "Mot de passe administrateur du serveur LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Synchronisation automatique",
"Auto Sync - Tooltip": "Configuration de synchronisation automatique, désactivée à 0",
"Base DN": "DN racine",
@ -844,6 +849,8 @@
"From name - Tooltip": "Le nom affiché comme expéditeur dans les e-mails envoyés",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Hôte",
"Host - Tooltip": "Nom d'hôte",
"IdP": "IdP (Identité Fournisseur)",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Posisi formulir",
"Form position - Tooltip": "Tempat pendaftaran, masuk, dan lupa kata sandi",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "URL ikon Favicon yang digunakan di semua halaman Casdoor organisasi",
"First name": "Nama depan",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Lupakan URL",
"Forget URL - Tooltip": "URL kustom untuk halaman \"Lupa kata sandi\". Jika tidak diatur, halaman \"Lupa kata sandi\" default Casdoor akan digunakan. Ketika diatur, tautan \"Lupa kata sandi\" pada halaman masuk akan diarahkan ke URL ini",
"Found some texts still not translated? Please help us translate at": "Menemukan beberapa teks yang masih belum diterjemahkan? Tolong bantu kami menerjemahkan di",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN atau ID dari administrator server LDAP",
"Admin Password": "Kata sandi administrator",
"Admin Password - Tooltip": "Kata sandi administrator server LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sinkronisasi",
"Auto Sync - Tooltip": "Konfigurasi auto-sync dimatikan pada 0",
"Base DN": "DN dasar",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Tuan rumah",
"Host - Tooltip": "Nama tuan rumah",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "フォームのポジション",
"Form position - Tooltip": "登録、ログイン、パスワード忘れフォームの位置",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "ファビコン",
"Favicon - Tooltip": "組織のすべてのCasdoorページに使用されるFaviconアイコンのURL",
"First name": "名前",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "URLを忘れてください",
"Forget URL - Tooltip": "「パスワードをお忘れの場合」ページのカスタムURL。未設定の場合、デフォルトのCasdoor「パスワードをお忘れの場合」ページが使用されます。設定された場合、ログインページの「パスワードをお忘れの場合」リンクはこのURLにリダイレクトされます",
"Found some texts still not translated? Please help us translate at": "まだ翻訳されていない文章が見つかりましたか?是非とも翻訳のお手伝いをお願いします",
@ -479,6 +482,8 @@
"Admin - Tooltip": "LDAPサーバー管理者のCNまたはID",
"Admin Password": "管理者パスワード",
"Admin Password - Tooltip": "LDAPサーバーの管理者パスワード",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "オート同期",
"Auto Sync - Tooltip": "自動同期の設定は、0で無効になっています",
"Base DN": "ベース DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "ホスト",
"Host - Tooltip": "ホストの名前",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "양식 위치",
"Form position - Tooltip": "가입, 로그인 및 비밀번호 재설정 양식의 위치",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "파비콘",
"Favicon - Tooltip": "조직의 모든 Casdoor 페이지에서 사용되는 Favicon 아이콘 URL",
"First name": "이름",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "URL을 잊어버려라",
"Forget URL - Tooltip": "\"비밀번호를 잊어버렸을 경우\" 페이지에 대한 사용자 정의 URL. 설정되지 않은 경우 기본 Casdoor \"비밀번호를 잊어버렸을 경우\" 페이지가 사용됩니다. 설정된 경우 로그인 페이지의 \"비밀번호를 잊으셨나요?\" 링크는 이 URL로 리디렉션됩니다",
"Found some texts still not translated? Please help us translate at": "아직 번역되지 않은 텍스트가 있나요? 번역에 도움을 주실 수 있나요?",
@ -479,6 +482,8 @@
"Admin - Tooltip": "LDAP 서버 관리자의 CN 또는 ID",
"Admin Password": "관리자 비밀번호",
"Admin Password - Tooltip": "LDAP 서버 관리자 비밀번호",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "자동 동기화",
"Auto Sync - Tooltip": "자동 동기화 구성, 0에서 비활성화됨",
"Base DN": "기본 DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "호스트",
"Host - Tooltip": "호스트의 이름",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Posição do formulário",
"Form position - Tooltip": "Localização dos formulários de registro, login e recuperação de senha",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "URL do ícone de favicon usado em todas as páginas do Casdoor da organização",
"First name": "Nome",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "URL de Esqueci a Senha",
"Forget URL - Tooltip": "URL personalizada para a página de \"Esqueci a senha\". Se não definido, será usada a página padrão de \"Esqueci a senha\" do Casdoor. Quando definido, o link de \"Esqueci a senha\" na página de login será redirecionado para esta URL",
"Found some texts still not translated? Please help us translate at": "Encontrou algum texto ainda não traduzido? Ajude-nos a traduzir em",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN ou ID do administrador do servidor LDAP",
"Admin Password": "Senha do Administrador",
"Admin Password - Tooltip": "Senha do administrador do servidor LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Sincronização Automática",
"Auto Sync - Tooltip": "Configuração de sincronização automática, desativada em 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Nome do remetente",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Nome do host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Позиция формы",
"Form position - Tooltip": "Местоположение форм регистрации, входа и восстановления пароля",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Фавикон",
"Favicon - Tooltip": "URL иконки Favicon, используемый на всех страницах организации Casdoor",
"First name": "Имя",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Забудьте URL",
"Forget URL - Tooltip": "Настроенный URL для страницы \"Забыли пароль\". Если не установлено, будет использоваться стандартная страница \"Забыли пароль\" Casdoor. При установке, ссылка \"Забыли пароль\" на странице входа будет перенаправляться на этот URL",
"Found some texts still not translated? Please help us translate at": "Нашли некоторые тексты, которые еще не переведены? Пожалуйста, помогите нам перевести на",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN или ID администратора сервера LDAP",
"Admin Password": "Пароль администратора",
"Admin Password - Tooltip": "Пароль администратора сервера LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Автораспределение",
"Auto Sync - Tooltip": "Автоматическая синхронизация настроек отключена при значении 0",
"Base DN": "Базовый DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Хост",
"Host - Tooltip": "Имя хоста",
"IdP": "ИдП",

View File

@ -65,6 +65,7 @@
"Footer HTML": "HTML päty",
"Footer HTML - Edit": "HTML päty - Upraviť",
"Footer HTML - Tooltip": "Vlastná pätka vašej aplikácie",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Pozícia formulára",
"Form position - Tooltip": "Miesto registračných, prihlasovacích a zabudnutých formulárov",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "URL ikony favicon používaná na všetkých stránkach Casdoor organizácie",
"First name": "Meno",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "URL zabudnutia",
"Forget URL - Tooltip": "Vlastná URL pre stránku \"Zabudol som heslo\". Ak nie je nastavená, bude použitá predvolená stránka Casdoor \"Zabudol som heslo\". Po nastavení bude odkaz na stránke prihlásenia presmerovaný na túto URL",
"Found some texts still not translated? Please help us translate at": "Našli ste nejaké texty, ktoré ešte nie sú preložené? Pomôžte nám preložiť na",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN alebo ID administrátora LDAP servera",
"Admin Password": "Heslo administrátora",
"Admin Password - Tooltip": "Heslo administrátora LDAP servera",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Automatická synchronizácia",
"Auto Sync - Tooltip": "Konfigurácia automatickej synchronizácie, zakázaná na 0",
"Base DN": "Základný DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Meno odosielateľa",
"Get phone number": "Získať telefónne číslo",
"Get phone number - Tooltip": "Ak je synchronizácia telefónneho čísla povolená, mali by ste najprv povoliť Google People API a pridať rozsah https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Hostiteľ",
"Host - Tooltip": "Názov hostiteľa",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "First name",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon icon URL used in all Casdoor pages of the organization",
"First name": "İsim",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Forget URL",
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
"Admin Password": "Admin Password",
"Admin Password - Tooltip": "LDAP server administrator password",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Auto Sync",
"Auto Sync - Tooltip": "Auto-sync configuration, disabled at 0",
"Base DN": "Base DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Нижній колонтитул HTML",
"Footer HTML - Edit": "Нижній колонтитул HTML - Редагувати",
"Footer HTML - Tooltip": "Налаштуйте нижній колонтитул вашої програми",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Положення форми",
"Form position - Tooltip": "Розташування форм для реєстрації, входу та забуття пароля",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "URL-адреса піктограми Favicon, яка використовується на всіх сторінках Casdoor організації",
"First name": "Ім'я",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Забути URL",
"Forget URL - Tooltip": "Користувацька URL-адреса для сторінки \"Забути пароль\". ",
"Found some texts still not translated? Please help us translate at": "Знайшли ще неперекладені тексти? ",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN або ID адміністратора сервера LDAP",
"Admin Password": "Пароль адміністратора",
"Admin Password - Tooltip": "Пароль адміністратора сервера LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Автоматична синхронізація",
"Auto Sync - Tooltip": "Конфігурація автоматичної синхронізації, вимкнена на 0",
"Base DN": "Базовий DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "Назва \"Від\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Хост",
"Host - Tooltip": "Ім'я хоста",
"IdP": "IDP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Forced redirect origin": "Forced redirect origin",
"Form position": "Vị trí của hình thức",
"Form position - Tooltip": "Vị trí của các biểu mẫu đăng ký, đăng nhập và quên mật khẩu",
"Generate Face ID": "Generate Face ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "AUD",
"BRL": "BRL",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
@ -277,6 +279,7 @@
"Favicon": "Favicon",
"Favicon - Tooltip": "URL biểu tượng Favicon được sử dụng trong tất cả các trang của tổ chức Casdoor",
"First name": "Tên",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "Quên URL",
"Forget URL - Tooltip": "Đường dẫn tùy chỉnh cho trang \"Quên mật khẩu\". Nếu không được thiết lập, trang \"Quên mật khẩu\" mặc định của Casdoor sẽ được sử dụng. Khi cài đặt, liên kết \"Quên mật khẩu\" trên trang đăng nhập sẽ chuyển hướng đến URL này",
"Found some texts still not translated? Please help us translate at": "Tìm thấy một số văn bản vẫn chưa được dịch? Vui lòng giúp chúng tôi dịch tại",
@ -479,6 +482,8 @@
"Admin - Tooltip": "CN hoặc ID của quản trị viên máy chủ LDAP",
"Admin Password": "Mật khẩu quản trị viên",
"Admin Password - Tooltip": "Mật khẩu quản trị viên của máy chủ LDAP",
"Allow self-signed certificate": "Allow self-signed certificate",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "Tự động đồng bộ hóa",
"Auto Sync - Tooltip": "Đồng bộ hóa tự động cấu hình, bị tắt tại 0",
"Base DN": "DN cơ sở",
@ -844,6 +849,8 @@
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Chủ nhà",
"Host - Tooltip": "Tên của người chủ chỗ ở",
"IdP": "IdP",

View File

@ -65,6 +65,7 @@
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "自定义应用的footer",
"Forced redirect origin": "强制重定向origin",
"Form position": "表单位置",
"Form position - Tooltip": "注册、登录、忘记密码等表单的位置",
"Generate Face ID": "生成人脸ID",
@ -168,6 +169,7 @@
},
"currency": {
"AUD": "澳大利亚元",
"BRL": "巴西雷亚尔",
"CAD": "加拿大元",
"CHF": "瑞士法郎",
"CNY": "人民币",
@ -277,6 +279,7 @@
"Favicon": "组织Favicon",
"Favicon - Tooltip": "该组织所有Casdoor页面中所使用的Favicon图标URL",
"First name": "名字",
"Forced redirect origin - Tooltip": "Forced redirect origin - Tooltip",
"Forget URL": "忘记密码URL",
"Forget URL - Tooltip": "自定义忘记密码页面的URL不设置时采用Casdoor默认的忘记密码页面设置后Casdoor各类页面的忘记密码链接会跳转到该URL",
"Found some texts still not translated? Please help us translate at": "发现有些文字尚未翻译?请移步这里帮我们翻译:",
@ -479,6 +482,8 @@
"Admin - Tooltip": "LDAP服务器管理员的CN或ID",
"Admin Password": "密码",
"Admin Password - Tooltip": "LDAP服务器管理员密码",
"Allow self-signed certificate": "允许自签名证书",
"Allow self-signed certificate - Tooltip": "Allow self-signed certificate - Tooltip",
"Auto Sync": "自动同步",
"Auto Sync - Tooltip": "自动同步配置为0时禁用",
"Base DN": "基本DN",
@ -844,6 +849,8 @@
"From name - Tooltip": "邮件里发件人的显示名称",
"Get phone number": "获取手机号码",
"Get phone number - Tooltip": "如果启用获取手机号码你需要先启用peopleApi并添加范围https://www.googleapis.com/auth/user.phonenumbers.read",
"HTTP header": "HTTP 标头",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "主机",
"Host - Tooltip": "主机名",
"IdP": "身份提供商",

View File

@ -0,0 +1,138 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Input, Table} from "antd";
import i18next from "i18next";
import {DeleteOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
class HttpHeaderTable extends React.Component {
constructor(props) {
super(props);
this.state = {
httpHeaders: [],
};
// transfer the Object to object[]
if (this.props.httpHeaders !== null) {
Object.entries(this.props.httpHeaders).map((item, index) => {
this.state.httpHeaders.push({key: index, name: item[0], value: item[1]});
});
}
}
page = 1;
pageSize = 10;
count = this.props.httpHeaders !== null ? Object.entries(this.props.httpHeaders).length : 0;
updateTable(table) {
this.setState({httpHeaders: table});
const httpHeaders = {};
table.map((item) => {
httpHeaders[item.name] = item.value;
});
this.props.onUpdateTable(httpHeaders);
}
addRow(table) {
const row = {key: this.count, name: "", value: ""};
if (table === undefined) {
table = [];
}
table = Setting.addRow(table, row);
this.count = this.count + 1;
this.updateTable(table);
}
deleteRow(table, index) {
table = Setting.deleteRow(table, this.getIndex(index));
this.updateTable(table);
}
getIndex(index) {
// Need to be used in all place when modify table. Parameter is the row index in table, need to calculate the index in dataSource.
return index + (this.page - 1) * this.pageSize;
}
updateField(table, index, key, value) {
table[this.getIndex(index)][key] = value;
this.updateTable(table);
}
renderTable(table) {
const columns = [
{
title: i18next.t("user:Keys"),
dataIndex: "name",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "name", e.target.value);
}} />
);
},
},
{
title: i18next.t("user:Values"),
dataIndex: "value",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "value", e.target.value);
}} />
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "operation",
width: "20px",
render: (text, record, index) => {
return (
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
);
},
},
];
return (
<Table title={() => (
<div>
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
pagination={{
defaultPageSize: this.pageSize,
onChange: page => {this.page = page;},
}}
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
/>
);
}
render() {
return (
<React.Fragment>
{
this.renderTable(this.state.httpHeaders)
}
</React.Fragment>
);
}
}
export default HttpHeaderTable;

View File

@ -66,14 +66,14 @@ class PrometheusInfoTable extends React.Component {
if (this.state.table === "latency") {
return (
<div style={{height: "300px", overflow: "auto"}}>
<Table columns={latencyColumns} dataSource={this.props.prometheusInfo.apiLatency} pagination={false} />
<Table columns={latencyColumns} dataSource={this.props.prometheusInfo?.apiLatency} pagination={false} />
</div>
);
} else if (this.state.table === "throughput") {
return (
<div style={{height: "300px", overflow: "auto"}}>
{i18next.t("system:Total Throughput")}: {this.props.prometheusInfo.totalThroughput}
<Table columns={throughputColumns} dataSource={this.props.prometheusInfo.apiThroughput} pagination={false} />
{i18next.t("system:Total Throughput")}: {this.props.prometheusInfo?.totalThroughput}
<Table columns={throughputColumns} dataSource={this.props.prometheusInfo?.apiThroughput} pagination={false} />
</div>
);
}