Compare commits

...

78 Commits

Author SHA1 Message Date
c699e35e6b feat: load theme from first HTML render cookie (#3505) 2025-01-18 19:04:16 +08:00
e28d90d0aa feat: support CUCloud SMN notification provider (#3502) 2025-01-17 08:35:31 +08:00
4fc7600865 feat: skip update user ranking if ranking not in accountItem (#3500) 2025-01-14 22:43:49 +08:00
19f62a461b feat: fix SAML's redirectUrl and POST ProtocolBinding (#3498) 2025-01-13 20:55:37 +08:00
7ddc2778c0 feat: show error message when organization doesn't have default application in invitation edit page (#3495)
* fix: inform user when organization haven't default application in signup page

* fix: include org name in the error message
2025-01-12 22:48:21 +08:00
b96fa2a995 feat: skip GetUserCount() if there is no quota limit (#3491) 2025-01-10 22:28:25 +08:00
fcfb73af6e feat: increase org password field length to 200 2025-01-09 20:07:49 +08:00
43bebc03b9 feat: fix crash in roleChangeTrigger() 2025-01-09 16:41:56 +08:00
c5f25cbc7d feat: getPidByPort() supports alpine now (#3483)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2025-01-08 12:18:46 +08:00
3feb6ce84d feat: add Kwai OAuth provider (#3480)
* feat: add Kwai OAuth provider

* fix: incorrect parameter in getAuthUrl
2025-01-08 00:09:16 +08:00
08d6b45fc5 feat: keeps "build" folder during yarn build 2025-01-07 23:38:50 +08:00
56d0de64dc feat: support StopOldInstance() 2025-01-07 21:39:21 +08:00
1813e8e8c7 feat: return goroutine error in get-dashboard API (#3479) 2025-01-07 10:35:45 +08:00
e27c764a55 feat: fix bug that GitHub oauth provider shows error if failed to fetch user's email (#3474)
* fix: fix github idp will stop login if it cannot fetch user's email through al restful api

* Update github.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2025-01-05 20:25:42 +08:00
e5a2057382 feat: fix empty scope bug in RefreshToken API (#3467)
* fix: fix scope will be empty when user not passing scope in refresh api

* fix: promote code format
2025-01-02 12:53:17 +08:00
8457ff7433 feat: support radiusDefaultOrganization in app.conf 2025-01-02 00:10:58 +08:00
888a6f2feb feat: add regex to restrict Email addresses in OAuth provider (#3465)
* feat: support use regex expression to limit email receiver address

* feat: limit in correct pos

* feat: promote code format

* feat: promote code format

* fix: fix linter issue
2025-01-02 00:00:57 +08:00
b57b64fc36 feat: add origin field for mfaAccountTable (#3463) 2024-12-29 22:51:21 +08:00
0d239ba1cf feat: improve the error message of GitHub OAuth provider (#3462) 2024-12-29 21:54:54 +08:00
8927e08217 feat: speed up GetDashboard() by only fetching last 30 days data (#3458)
* feat: only check 30 days data

* refactor: refactor GetDashboard to reduce code line

* refactor: refactor GetDashboard to reduce code line

* refactor: remove unused where

* fix: fix error code
2024-12-29 16:15:52 +08:00
0636069584 feat: only fetch created_time field to reduce data size in get-dashboard API (#3457) 2024-12-28 23:52:19 +08:00
4d0f73c84e feat: fix Casdoor OAuth provider doesn't use domain field bug 2024-12-28 10:01:56 +08:00
74a2478e10 feat: Make MinIO storage provider region setting configurable (#3433)
* fix: Make MinIO provider region setting configurable

* Fix: Correct the issue where modifications to MinIO's default logic caused behavioral discrepancies
2024-12-23 16:07:14 +08:00
acc6f3e887 feat: escape the avatal URL in CAS response (#3434) 2024-12-20 17:11:58 +08:00
185ab9750a feat: fix VerificationRecord.IsUsed JSON Field Mapping 2024-12-18 13:56:54 +08:00
48adc050d6 feat: can pass empty user id on user update (#3443) 2024-12-18 07:56:44 +08:00
b0e318c9db feat: add localized tab titles for Basic and Advanced Editors (#3431)
* feat: add localized tab titles for Basic and Advanced Editors

* docs: update translations for model editor labels in multiple locales
2024-12-16 08:34:13 +08:00
f9a6efc00f feat: advanced model editor should support changing UI language (#3430) 2024-12-15 15:53:29 +08:00
bd4a6775dd feat: get github user email with user/emails api (#3428)
* feat: get user email use `user/emails` api

* feat: improve code format

* feat: improve code format
2024-12-15 10:28:18 +08:00
e3a43d0062 feat: improve the advanced editor of model edit page (#3427) 2024-12-15 02:07:02 +08:00
0cf281cac0 feat: fix record's password regex bug (#3421) 2024-12-11 08:43:03 +08:00
7322f67ae0 feat: add model, adapter and enforcer to the dashboard page chart (#3413)
* [feature] Add more data (Model, Adapter, Enforcer) to the dashboard page chart #3379

* feat: add model, adapter, enforcer to dashboard
2024-12-09 16:07:39 +08:00
b927c6d7b4 feat: support LDAP's SetPassword (#3395)
* fix: Resolve the issue mentioned in #3392

* fix: Change checkLdapUserPassword to CheckLdapUserPassword.

* fix: the issue mentioned by hsluoyz.

* fix: Check if the user parameter is nil

* fix: use existing i18n message
2024-12-09 16:06:24 +08:00
01212cd1f3 feat: add AiAssistantUrl to frontend config (#3385) 2024-12-08 20:44:28 +08:00
bf55f94d41 feat: support CUCloud OSS storage provider (#3400) 2024-12-08 20:24:38 +08:00
f14711d315 feat: fix frontend bug 2024-12-07 21:53:01 +08:00
58e1c28f7c feat: support LDAPS protocol (#3390)
* feat: support ldaps

* fix: unencrypted port 389 not work after enable SSL
fix: remove useless conf and set ldapsCertId to empty
fix: return and log getTLSconfig error

* fix: remove unused setting

* fix: check nil condition

* fix: not log fail when certId is empty
2024-12-07 21:26:07 +08:00
922b19c64b feat: reduce i18n items 2024-12-07 21:22:57 +08:00
1d21c3fa90 feat: fix issue that introspectionResponse uses Bearer instead of raw tokenType (#3399) 2024-12-05 20:59:30 +08:00
6175fd6764 feat: make token_type_hint optional (#3397) 2024-12-04 20:10:15 +08:00
2ceb54f058 feat: support most popular currencies (#3388) 2024-12-01 21:46:44 +08:00
aaeaa7fefa feat: update go sms sender (#3386) 2024-11-29 23:00:34 +08:00
d522247552 feat: fix countryCode param bug in MFA login (#3384) 2024-11-29 21:46:06 +08:00
79dbdab6c9 feat: fix "dest is missing" bug in MFA login (#3383)
* feat: support stateless mfa setup

* Revert "feat: support stateless mfa setup"

This reverts commit bd843b2ff3.

* feat: use new implement

* fix: missing set field on login
2024-11-29 19:59:30 +08:00
fe40910e3b feat: support stateless MFA setup (#3382) 2024-11-29 19:50:10 +08:00
2d1736f13a feat: Add more data to the dashboard page chart #3365 (#3375)
* test

* feat: #3365 add more dada to the dashboard page chart

* feat: #3365 Add more data to the dashboard page chart
2024-11-26 09:16:35 +08:00
12b4d1c7cd feat: change LDAP attribute from cn to title for correct username mapping (#3378) 2024-11-26 09:13:05 +08:00
a45d2b87c1 feat: Add translations for Persian (#3372) 2024-11-23 16:24:07 +08:00
8484465d09 feat: fix SAML failed to redirect issue when login api returns RequiredMfa (#3364) 2024-11-21 20:31:56 +08:00
dff65eee20 feat: Force users to change their passwords after 3/6/12 months (#3352)
* feat: Force users to change their passwords after 3/6/12 months

* feat: Check if the password has expired by using the last_change_password_time field added to the user table

* feat: Use the created_time field of the user table to aid password expiration checking

* feat: Rename variable
2024-11-19 21:06:52 +08:00
596016456c feat: update CI's upload-artifact and download-artifact actions to v4 (#3361)
v3 of `actions/upload-artifact` and `actions/download-artifact` will be
fully deprecated by 5 December 2024. Jobs that are scheduled to run
during the brownout periods will also fail. See [1][2].

[1]: https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/
[2]: https://github.blog/changelog/2024-11-05-notice-of-breaking-changes-for-github-actions/

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-11-19 00:07:59 +08:00
673261c258 feat: fix placeholder bug in signin page (#3359) 2024-11-17 00:14:26 +08:00
3c5985a3c0 fix: fix several bugs in samlRequest (#3358) 2024-11-17 00:14:04 +08:00
4f3d62520a feat: fix the dashboard page shows zero data in mobile phone (#3356) 2024-11-16 22:02:49 +08:00
96f8b3d937 feat: fix SAML metadata URL and XML generation issue when enablePostBinding is enabled (#3354) 2024-11-16 15:35:30 +08:00
7ab5a5ade1 feat: add processArgsToTempFiles() to RunCasbinCommand() 2024-11-15 20:25:48 +08:00
5cbd0a96ca Use json format for argString in RunCasbinCommand() 2024-11-15 18:27:25 +08:00
7ccd8c4d4f feat: add RunCasbinCommand() API 2024-11-15 17:44:57 +08:00
b0fa3fc484 feat: add Casbin CLI API to Casdoor (#3351) 2024-11-15 16:10:22 +08:00
af01c4226a feat: add Organization.PasswordExpireDays field 2024-11-15 11:33:28 +08:00
7a3d85a29a feat: update github token to fix CI cannot release issue (#3348) 2024-11-14 18:05:56 +08:00
fd5ccd8d41 feat: support copying token to clipboard for casdoor-app (#3345)
* feat: support copy token to clipboard for casdoor-app auth

* feat: abstract casdoor-app related code
2024-11-13 17:06:09 +08:00
a439c5195d feat: get token only by hash now, remove get-by-value backward-compatible code 2024-11-13 17:04:27 +08:00
ba2e997d54 feat: fix CheckUpdateUser() logic to fix add-user error 2024-11-06 08:34:13 +08:00
0818de85d1 feat: fix username checks when organization.UseEmailAsUsername is enabled (#3329)
* feat: Username support email format

* feat: Only fulfill the first requirement

* fix: Improve code robustness
2024-11-05 20:38:47 +08:00
457c6098a4 feat: fix MFA empty CountryCode bug and show MFA error better in frontend 2024-11-04 16:17:24 +08:00
60f979fbb5 feat: fix MfaSetupPage empty bug when user's signup application is empty 2024-11-04 00:04:47 +08:00
ff53e44fa6 feat: use virtual select UI in role edit page (#3322) 2024-11-03 20:05:34 +08:00
1832de47db feat: fix bug in CheckEntryIp() 2024-11-03 20:00:52 +08:00
535eb0c465 fix: fix IP Whitelist field bug in application edit page 2024-11-03 19:55:59 +08:00
c190634cf3 feat: show Domain field for Qiniu storage provider (#3318)
allow Qiniu Provider to edit the Domain property in the edit page.
2024-10-27 14:10:58 +08:00
f7559aa040 feat: set created time if not presented in AddUser() API (#3315) 2024-10-24 23:06:05 +08:00
1e0b709c73 feat: pass signin method to CAS login to fix bug (#3313) 2024-10-24 14:56:12 +08:00
c0800b7fb3 feat: add util.IsValidOrigin() to improve CORS filter (#3301)
* fix: CORS check issue

* fix: promote format

* fix: promote format

* fix: promote format

* fix: promote format

* Update application.go

* Update cors_filter.go

* Update validation.go

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-10-20 20:09:21 +08:00
6fcdad2100 feat: fix bug that fails to login when PasswordObfuscator is enabled (#3299) 2024-10-19 23:09:59 +08:00
69d26d5c21 feat: add-user/update-user API should check if username/id/email/phone has duplicated with existing user (#3295) 2024-10-18 22:18:37 +08:00
94e6b5ecb8 feat: fix bug in SetPassword() API (#3296) 2024-10-18 20:50:43 +08:00
95e8bdcd36 feat: add initDataNewOnly to app.conf to skip overriding existing data in initDataFromFile() (#3294)
* feat: support control whether overwrite existing data during initDataFromFile

* feat: change conf var name

* feat: change conf var name
2024-10-18 00:08:08 +08:00
109 changed files with 3721 additions and 1865 deletions

View File

@ -114,12 +114,12 @@ jobs:
wait-on-timeout: 210 wait-on-timeout: 210
working-directory: ./web working-directory: ./web
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
if: failure() if: failure()
with: with:
name: cypress-screenshots name: cypress-screenshots
path: ./web/cypress/screenshots path: ./web/cypress/screenshots
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: cypress-videos name: cypress-videos
@ -147,7 +147,7 @@ jobs:
- name: Release - name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release run: yarn global add semantic-release@17.4.4 && semantic-release
env: env:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch Current version - name: Fetch Current version
id: get-current-tag id: get-current-tag

View File

@ -98,6 +98,7 @@ p, *, *, GET, /api/get-organization-names, *, *
p, *, *, GET, /api/get-all-objects, *, * p, *, *, GET, /api/get-all-objects, *, *
p, *, *, GET, /api/get-all-actions, *, * p, *, *, GET, /api/get-all-actions, *, *
p, *, *, GET, /api/get-all-roles, *, * p, *, *, GET, /api/get-all-roles, *, *
p, *, *, GET, /api/run-casbin-command, *, *
p, *, *, GET, /api/get-invitation-info, *, * p, *, *, GET, /api/get-invitation-info, *, *
p, *, *, GET, /api/faceid-signin-begin, *, * p, *, *, GET, /api/faceid-signin-begin, *, *
` `

View File

@ -25,9 +25,13 @@ enableErrorMask = false
enableGzip = true enableGzip = true
inactiveTimeoutMinutes = inactiveTimeoutMinutes =
ldapServerPort = 389 ldapServerPort = 389
ldapsCertId = ""
ldapsServerPort = 636
radiusServerPort = 1812 radiusServerPort = 1812
radiusDefaultOrganization = "built-in"
radiusSecret = "secret" radiusSecret = "secret"
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1} quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"} logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataNewOnly = false
initDataFile = "./init_data.json" initDataFile = "./init_data.json"
frontendBaseDir = "../casdoor" frontendBaseDir = "../cc_0"

View File

@ -22,6 +22,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -617,6 +618,17 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error())) c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
return return
} }
if provider.EmailRegex != "" {
reg, err := regexp.Compile(provider.EmailRegex)
if err != nil {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
return
}
if !reg.MatchString(userInfo.Email) {
c.ResponseError(fmt.Sprintf(c.T("check:Email is invalid")))
}
}
} }
if authForm.Method == "signup" { if authForm.Method == "signup" {
@ -854,6 +866,7 @@ func (c *ApiController) Login() {
} }
if authForm.Passcode != "" { if authForm.Passcode != "" {
user.CountryCode = user.GetCountryCode(user.CountryCode)
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false)) mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
if mfaUtil == nil { if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type") c.ResponseError("Invalid multi-factor authentication type")

View File

@ -0,0 +1,114 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
)
func processArgsToTempFiles(args []string) ([]string, []string, error) {
tempFiles := []string{}
newArgs := []string{}
for i := 0; i < len(args); i++ {
if (args[i] == "-m" || args[i] == "-p") && i+1 < len(args) {
pattern := fmt.Sprintf("casbin_temp_%s_*.conf", args[i])
tempFile, err := os.CreateTemp("", pattern)
if err != nil {
return nil, nil, fmt.Errorf("failed to create temp file: %v", err)
}
_, err = tempFile.WriteString(args[i+1])
if err != nil {
tempFile.Close()
return nil, nil, fmt.Errorf("failed to write to temp file: %v", err)
}
tempFile.Close()
tempFiles = append(tempFiles, tempFile.Name())
newArgs = append(newArgs, args[i], tempFile.Name())
i++
} else {
newArgs = append(newArgs, args[i])
}
}
return tempFiles, newArgs, nil
}
// RunCasbinCommand
// @Title RunCasbinCommand
// @Tag Enforcer API
// @Description Call Casbin CLI commands
// @Success 200 {object} controllers.Response The Response object
// @router /run-casbin-command [get]
func (c *ApiController) RunCasbinCommand() {
language := c.Input().Get("language")
argString := c.Input().Get("args")
if language == "" {
language = "go"
}
// use "casbin-go-cli" by default, can be also "casbin-java-cli", "casbin-node-cli", etc.
// the pre-built binary of "casbin-go-cli" can be found at: https://github.com/casbin/casbin-go-cli/releases
binaryName := fmt.Sprintf("casbin-%s-cli", language)
_, err := exec.LookPath(binaryName)
if err != nil {
c.ResponseError(fmt.Sprintf("executable file: %s not found in PATH", binaryName))
return
}
// RBAC model & policy example:
// https://door.casdoor.com/api/run-casbin-command?language=go&args=["enforce", "-m", "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) %26%26 r.obj == p.obj %26%26 r.act == p.act", "-p", "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin", "alice", "data1", "read"]
// Casbin CLI usage:
// https://github.com/jcasbin/casbin-java-cli?tab=readme-ov-file#get-started
var args []string
err = json.Unmarshal([]byte(argString), &args)
if err != nil {
c.ResponseError(err.Error())
return
}
tempFiles, processedArgs, err := processArgsToTempFiles(args)
defer func() {
for _, file := range tempFiles {
os.Remove(file)
}
}()
if err != nil {
c.ResponseError(err.Error())
return
}
command := exec.Command(binaryName, processedArgs...)
outputBytes, err := command.CombinedOutput()
if err != nil {
errorString := err.Error()
if outputBytes != nil {
output := string(outputBytes)
errorString = fmt.Sprintf("%s, error: %s", output, err.Error())
}
c.ResponseError(errorString)
return
}
output := string(outputBytes)
output = strings.TrimSuffix(output, "\n")
c.ResponseOk(output)
}

View File

@ -22,13 +22,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
const (
MfaRecoveryCodesSession = "mfa_recovery_codes"
MfaCountryCodeSession = "mfa_country_code"
MfaDestSession = "mfa_dest"
MfaTotpSecretSession = "mfa_totp_secret"
)
// MfaSetupInitiate // MfaSetupInitiate
// @Title MfaSetupInitiate // @Title MfaSetupInitiate
// @Tag MFA API // @Tag MFA API
@ -72,11 +65,6 @@ func (c *ApiController) MfaSetupInitiate() {
} }
recoveryCode := uuid.NewString() recoveryCode := uuid.NewString()
c.SetSession(MfaRecoveryCodesSession, recoveryCode)
if mfaType == object.TotpType {
c.SetSession(MfaTotpSecretSession, mfaProps.Secret)
}
mfaProps.RecoveryCodes = []string{recoveryCode} mfaProps.RecoveryCodes = []string{recoveryCode}
resp := mfaProps resp := mfaProps
@ -94,6 +82,9 @@ func (c *ApiController) MfaSetupInitiate() {
func (c *ApiController) MfaSetupVerify() { func (c *ApiController) MfaSetupVerify() {
mfaType := c.Ctx.Request.Form.Get("mfaType") mfaType := c.Ctx.Request.Form.Get("mfaType")
passcode := c.Ctx.Request.Form.Get("passcode") passcode := c.Ctx.Request.Form.Get("passcode")
secret := c.Ctx.Request.Form.Get("secret")
dest := c.Ctx.Request.Form.Get("dest")
countryCode := c.Ctx.Request.Form.Get("countryCode")
if mfaType == "" || passcode == "" { if mfaType == "" || passcode == "" {
c.ResponseError("missing auth type or passcode") c.ResponseError("missing auth type or passcode")
@ -104,32 +95,28 @@ func (c *ApiController) MfaSetupVerify() {
MfaType: mfaType, MfaType: mfaType,
} }
if mfaType == object.TotpType { if mfaType == object.TotpType {
secret := c.GetSession(MfaTotpSecretSession) if secret == "" {
if secret == nil {
c.ResponseError("totp secret is missing") c.ResponseError("totp secret is missing")
return return
} }
config.Secret = secret.(string) config.Secret = secret
} else if mfaType == object.SmsType { } else if mfaType == object.SmsType {
dest := c.GetSession(MfaDestSession) if dest == "" {
if dest == nil {
c.ResponseError("destination is missing") c.ResponseError("destination is missing")
return return
} }
config.Secret = dest.(string) config.Secret = dest
countryCode := c.GetSession(MfaCountryCodeSession) if countryCode == "" {
if countryCode == nil {
c.ResponseError("country code is missing") c.ResponseError("country code is missing")
return return
} }
config.CountryCode = countryCode.(string) config.CountryCode = countryCode
} else if mfaType == object.EmailType { } else if mfaType == object.EmailType {
dest := c.GetSession(MfaDestSession) if dest == "" {
if dest == nil {
c.ResponseError("destination is missing") c.ResponseError("destination is missing")
return return
} }
config.Secret = dest.(string) config.Secret = dest
} }
mfaUtil := object.GetMfaUtil(mfaType, config) mfaUtil := object.GetMfaUtil(mfaType, config)
@ -159,6 +146,10 @@ func (c *ApiController) MfaSetupEnable() {
owner := c.Ctx.Request.Form.Get("owner") owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name") name := c.Ctx.Request.Form.Get("name")
mfaType := c.Ctx.Request.Form.Get("mfaType") mfaType := c.Ctx.Request.Form.Get("mfaType")
secret := c.Ctx.Request.Form.Get("secret")
dest := c.Ctx.Request.Form.Get("dest")
countryCode := c.Ctx.Request.Form.Get("secret")
recoveryCodes := c.Ctx.Request.Form.Get("recoveryCodes")
user, err := object.GetUser(util.GetId(owner, name)) user, err := object.GetUser(util.GetId(owner, name))
if err != nil { if err != nil {
@ -176,43 +167,39 @@ func (c *ApiController) MfaSetupEnable() {
} }
if mfaType == object.TotpType { if mfaType == object.TotpType {
secret := c.GetSession(MfaTotpSecretSession) if secret == "" {
if secret == nil {
c.ResponseError("totp secret is missing") c.ResponseError("totp secret is missing")
return return
} }
config.Secret = secret.(string) config.Secret = secret
} else if mfaType == object.EmailType { } else if mfaType == object.EmailType {
if user.Email == "" { if user.Email == "" {
dest := c.GetSession(MfaDestSession) if dest == "" {
if dest == nil {
c.ResponseError("destination is missing") c.ResponseError("destination is missing")
return return
} }
user.Email = dest.(string) user.Email = dest
} }
} else if mfaType == object.SmsType { } else if mfaType == object.SmsType {
if user.Phone == "" { if user.Phone == "" {
dest := c.GetSession(MfaDestSession) if dest == "" {
if dest == nil {
c.ResponseError("destination is missing") c.ResponseError("destination is missing")
return return
} }
user.Phone = dest.(string) user.Phone = dest
countryCode := c.GetSession(MfaCountryCodeSession) if countryCode == "" {
if countryCode == nil {
c.ResponseError("country code is missing") c.ResponseError("country code is missing")
return return
} }
user.CountryCode = countryCode.(string) user.CountryCode = countryCode
} }
} }
recoveryCodes := c.GetSession(MfaRecoveryCodesSession)
if recoveryCodes == nil { if recoveryCodes == "" {
c.ResponseError("recovery codes is missing") c.ResponseError("recovery codes is missing")
return return
} }
config.RecoveryCodes = []string{recoveryCodes.(string)} config.RecoveryCodes = []string{recoveryCodes}
mfaUtil := object.GetMfaUtil(mfaType, config) mfaUtil := object.GetMfaUtil(mfaType, config)
if mfaUtil == nil { if mfaUtil == nil {
@ -226,14 +213,6 @@ func (c *ApiController) MfaSetupEnable() {
return return
} }
c.DelSession(MfaRecoveryCodesSession)
if mfaType == object.TotpType {
c.DelSession(MfaTotpSecretSession)
} else {
c.DelSession(MfaCountryCodeSession)
c.DelSession(MfaDestSession)
}
c.ResponseOk(http.StatusText(http.StatusOK)) c.ResponseOk(http.StatusText(http.StatusOK))
} }

View File

@ -322,17 +322,22 @@ func (c *ApiController) IntrospectToken() {
} }
tokenTypeHint := c.Input().Get("token_type_hint") tokenTypeHint := c.Input().Get("token_type_hint")
token, err := object.GetTokenByTokenValue(tokenValue, tokenTypeHint) var token *object.Token
if err != nil { if tokenTypeHint != "" {
c.ResponseTokenError(err.Error()) token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
return if err != nil {
} c.ResponseTokenError(err.Error())
if token == nil { return
c.Data["json"] = &object.IntrospectionResponse{Active: false} }
c.ServeJSON() if token == nil {
return c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
} }
var introspectionResponse object.IntrospectionResponse
if application.TokenFormat == "JWT-Standard" { if application.TokenFormat == "JWT-Standard" {
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application) jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
if err != nil || jwtToken.Valid() != nil { if err != nil || jwtToken.Valid() != nil {
@ -344,12 +349,37 @@ func (c *ApiController) IntrospectToken() {
return return
} }
c.Data["json"] = &object.IntrospectionResponse{ introspectionResponse = object.IntrospectionResponse{
Active: true, Active: true,
Scope: jwtToken.Scope, Scope: jwtToken.Scope,
ClientId: clientId, ClientId: clientId,
Username: token.User, Username: jwtToken.Name,
TokenType: token.TokenType, 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,
}
} else {
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
if err != nil || jwtToken.Valid() != nil {
// 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()
return
}
introspectionResponse = object.IntrospectionResponse{
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: jwtToken.Name,
TokenType: jwtToken.TokenType,
Exp: jwtToken.ExpiresAt.Unix(), Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(), Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(), Nbf: jwtToken.NotBefore.Unix(),
@ -358,33 +388,22 @@ func (c *ApiController) IntrospectToken() {
Iss: jwtToken.Issuer, Iss: jwtToken.Issuer,
Jti: jwtToken.ID, Jti: jwtToken.ID,
} }
c.ServeJSON()
return
} }
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application) if tokenTypeHint == "" {
if err != nil || jwtToken.Valid() != nil { token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
// and token revoked case. but we not implement if err != nil {
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs. c.ResponseTokenError(err.Error())
// refs: https://tools.ietf.org/html/rfc7009 return
c.Data["json"] = &object.IntrospectionResponse{Active: false} }
c.ServeJSON() if token == nil {
return c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
} }
introspectionResponse.TokenType = token.TokenType
c.Data["json"] = &object.IntrospectionResponse{ c.Data["json"] = introspectionResponse
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: token.User,
TokenType: token.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,
}
c.ServeJSON() c.ServeJSON()
} }

View File

@ -353,28 +353,18 @@ func (c *ApiController) AddUser() {
return return
} }
count, err := object.GetUserCount("", "", "", "") if err := checkQuotaForUser(); err != nil {
if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if err := checkQuotaForUser(int(count)); err != nil { emptyUser := object.User{}
c.ResponseError(err.Error()) msg := object.CheckUpdateUser(&emptyUser, &user, c.GetAcceptLanguage())
return
}
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
if msg != "" { if msg != "" {
c.ResponseError(msg) c.ResponseError(msg)
return return
} }
if err = object.CheckIpWhitelist(user.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddUser(&user)) c.Data["json"] = wrapActionResponse(object.AddUser(&user))
c.ServeJSON() c.ServeJSON()
} }
@ -479,6 +469,16 @@ func (c *ApiController) SetPassword() {
userId := util.GetId(userOwner, userName) userId := util.GetId(userOwner, userName)
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
if requestUserId == "" && code == "" { if requestUserId == "" && code == "" {
c.ResponseError(c.T("general:Please login first"), "Please login first") c.ResponseError(c.T("general:Please login first"), "Please login first")
@ -494,7 +494,12 @@ func (c *ApiController) SetPassword() {
c.ResponseError(c.T("general:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
if userId != c.GetSession("verifiedUserId") {
c.ResponseError(c.T("general:Wrong userId"))
return
}
c.SetSession("verifiedCode", "") c.SetSession("verifiedCode", "")
c.SetSession("verifiedUserId", "")
} }
targetUser, err := object.GetUser(userId) targetUser, err := object.GetUser(userId)
@ -517,7 +522,11 @@ func (c *ApiController) SetPassword() {
} }
} }
} else if code == "" { } else if code == "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage()) if user.Ldap == "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
} else {
err = object.CheckLdapUserPassword(targetUser, oldPassword, c.GetAcceptLanguage())
}
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@ -560,8 +569,14 @@ func (c *ApiController) SetPassword() {
targetUser.Password = newPassword targetUser.Password = newPassword
targetUser.UpdateUserPassword(organization) targetUser.UpdateUserPassword(organization)
targetUser.NeedUpdatePassword = false targetUser.NeedUpdatePassword = false
targetUser.LastChangePasswordTime = util.GetCurrentTime()
if user.Ldap == "" {
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
} else {
err = object.ResetLdapPassword(targetUser, newPassword, c.GetAcceptLanguage())
}
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type"}, false)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@ -294,12 +294,18 @@ func checkQuotaForProvider(count int) error {
return nil return nil
} }
func checkQuotaForUser(count int) error { func checkQuotaForUser() error {
quota := conf.GetConfigQuota().User quota := conf.GetConfigQuota().User
if quota == -1 { if quota == -1 {
return nil return nil
} }
if count >= quota {
count, err := object.GetUserCount("", "", "", "")
if err != nil {
return err
}
if int(count) >= quota {
return fmt.Errorf("user quota is exceeded") return fmt.Errorf("user quota is exceeded")
} }
return nil return nil

View File

@ -246,8 +246,6 @@ func (c *ApiController) SendVerificationCode() {
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest { if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret vform.Dest = mfaProps.Secret
} }
} else if vform.Method == MfaSetupVerification {
c.SetSession(MfaDestSession, vform.Dest)
} }
provider, err = application.GetEmailProvider(vform.Method) provider, err = application.GetEmailProvider(vform.Method)
@ -282,11 +280,6 @@ func (c *ApiController) SendVerificationCode() {
vform.CountryCode = user.GetCountryCode(vform.CountryCode) vform.CountryCode = user.GetCountryCode(vform.CountryCode)
} }
} }
if vform.Method == MfaSetupVerification {
c.SetSession(MfaCountryCodeSession, vform.CountryCode)
c.SetSession(MfaDestSession, vform.Dest)
}
} else if vform.Method == MfaAuthVerification { } else if vform.Method == MfaAuthVerification {
mfaProps := user.GetPreferredMfaProps(false) mfaProps := user.GetPreferredMfaProps(false)
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest { if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
@ -294,6 +287,7 @@ func (c *ApiController) SendVerificationCode() {
} }
vform.CountryCode = mfaProps.CountryCode vform.CountryCode = mfaProps.CountryCode
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
} }
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode) provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
@ -533,5 +527,6 @@ func (c *ApiController) VerifyCode() {
} }
c.SetSession("verifiedCode", authForm.Code) c.SetSession("verifiedCode", authForm.Code)
c.SetSession("verifiedUserId", user.GetId())
c.ResponseOk() c.ResponseOk()
} }

5
go.mod
View File

@ -9,10 +9,10 @@ require (
github.com/beego/beego v1.12.12 github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.77.2 github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.24.0 github.com/casdoor/go-sms-sender v0.25.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/ldapserver v1.2.0 github.com/casdoor/ldapserver v1.2.0
github.com/casdoor/notify v0.45.0 github.com/casdoor/notify v1.0.0
github.com/casdoor/oss v1.8.0 github.com/casdoor/oss v1.8.0
github.com/casdoor/xorm-adapter/v3 v3.1.0 github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.4.0 github.com/casvisor/casvisor-go-sdk v1.4.0
@ -63,6 +63,7 @@ require (
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.21.0
golang.org/x/net v0.21.0 golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.17.0 golang.org/x/oauth2 v0.17.0
golang.org/x/text v0.14.0
google.golang.org/api v0.150.0 google.golang.org/api v0.150.0
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0

8
go.sum
View File

@ -1087,14 +1087,14 @@ github.com/casdoor/casdoor-go-sdk v0.50.0 h1:bUYbz/MzJuWfLKJbJM0+U0YpYewAur+THp5
github.com/casdoor/casdoor-go-sdk v0.50.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4= github.com/casdoor/casdoor-go-sdk v0.50.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4=
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs= github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc= github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM= github.com/casdoor/go-sms-sender v0.25.0 h1:eF4cOCSbjVg7+0uLlJQnna/FQ0BWW+Fp/x4cXhzQu1Y=
github.com/casdoor/go-sms-sender v0.24.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0= github.com/casdoor/go-sms-sender v0.25.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w= github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q= github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow= github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s= github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk= github.com/casdoor/notify v1.0.0 h1:oldsaaQFPrlufm/OA314z8DwFVE1Tc9Gt1z4ptRHhXw=
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ= github.com/casdoor/notify v1.0.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM= github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U= github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk= github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=

View File

@ -1,167 +1,167 @@
{ {
"account": { "account": {
"Failed to add user": "Failed to add user", "Failed to add user": "عدم موفقیت در افزودن کاربر",
"Get init score failed, error: %w": "Get init score failed, error: %w", "Get init score failed, error: %w": "عدم موفقیت در دریافت امتیاز اولیه، خطا: %w",
"Please sign out first": "Please sign out first", "Please sign out first": "لطفاً ابتدا خارج شوید",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "برنامه اجازه ثبت‌نام حساب جدید را نمی‌دهد"
}, },
"auth": { "auth": {
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "روش چالش باید S256 باشد",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s", "Failed to create user, user information is invalid: %s": "عدم موفقیت در ایجاد کاربر، اطلاعات کاربر نامعتبر است: %s",
"Failed to login in: %s": "Failed to login in: %s", "Failed to login in: %s": "عدم موفقیت در ورود: %s",
"Invalid token": "Invalid token", "Invalid token": "توکن نامعتبر",
"State expected: %s, but got: %s": "State expected: %s, but got: %s", "State expected: %s, but got: %s": "وضعیت مورد انتظار: %s، اما دریافت شد: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "حساب برای ارائه‌دهنده: %s و نام کاربری: %s (%s) وجود ندارد و مجاز به ثبت‌نام به‌عنوان حساب جدید از طریق %%s نیست، لطفاً از روش دیگری برای ثبت‌نام استفاده کنید",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "حساب برای ارائه‌دهنده: %s و نام کاربری: %s (%s) وجود ندارد و مجاز به ثبت‌نام به‌عنوان حساب جدید نیست، لطفاً با پشتیبانی IT خود تماس بگیرید",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "حساب برای ارائه‌دهنده: %s و نام کاربری: %s (%s) در حال حاضر به حساب دیگری مرتبط است: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "برنامه: %s وجود ندارد",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application", "The login method: login with LDAP is not enabled for the application": "روش ورود: ورود با LDAP برای برنامه فعال نیست",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application", "The login method: login with SMS is not enabled for the application": "روش ورود: ورود با پیامک برای برنامه فعال نیست",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application", "The login method: login with email is not enabled for the application": "روش ورود: ورود با ایمیل برای برنامه فعال نیست",
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application", "The login method: login with face is not enabled for the application": "روش ورود: ورود با چهره برای برنامه فعال نیست",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application", "The login method: login with password is not enabled for the application": "روش ورود: ورود با رمز عبور برای برنامه فعال نیست",
"The organization: %s does not exist": "The organization: %s does not exist", "The organization: %s does not exist": "سازمان: %s وجود ندارد",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "ارائه‌دهنده: %s برای برنامه فعال نیست",
"Unauthorized operation": "Unauthorized operation", "Unauthorized operation": "عملیات غیرمجاز",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "Unknown authentication type (not password or provider), form = %s": "نوع احراز هویت ناشناخته (نه رمز عبور و نه ارائه‌دهنده)، فرم = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags", "User's tag: %s is not listed in the application's tags": "برچسب کاربر: %s در برچسب‌های برنامه فهرست نشده است",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing" "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "کاربر پرداختی %s اشتراک فعال یا در انتظار ندارد و برنامه: %s قیمت‌گذاری پیش‌فرض ندارد"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "سرویس %s و %s مطابقت ندارند"
}, },
"check": { "check": {
"Affiliation cannot be blank": "Affiliation cannot be blank", "Affiliation cannot be blank": "وابستگی نمی‌تواند خالی باشد",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules", "Default code does not match the code's matching rules": "کد پیش‌فرض با قوانین تطبیق کد مطابقت ندارد",
"DisplayName cannot be blank": "DisplayName cannot be blank", "DisplayName cannot be blank": "نام نمایشی نمی‌تواند خالی باشد",
"DisplayName is not valid real name": "DisplayName is not valid real name", "DisplayName is not valid real name": "نام نمایشی یک نام واقعی معتبر نیست",
"Email already exists": "Email already exists", "Email already exists": "ایمیل قبلاً وجود دارد",
"Email cannot be empty": "Email cannot be empty", "Email cannot be empty": "ایمیل نمی‌تواند خالی باشد",
"Email is invalid": "Email is invalid", "Email is invalid": "ایمیل نامعتبر است",
"Empty username.": "Empty username.", "Empty username.": "نام کاربری خالی است.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in", "Face data does not exist, cannot log in": "داده‌های چهره وجود ندارد، نمی‌توان وارد شد",
"Face data mismatch": "Face data mismatch", "Face data mismatch": "عدم تطابق داده‌های چهره",
"FirstName cannot be blank": "FirstName cannot be blank", "FirstName cannot be blank": "نام نمی‌تواند خالی باشد",
"Invitation code cannot be blank": "Invitation code cannot be blank", "Invitation code cannot be blank": "کد دعوت نمی‌تواند خالی باشد",
"Invitation code exhausted": "Invitation code exhausted", "Invitation code exhausted": "کد دعوت استفاده شده است",
"Invitation code is invalid": "Invitation code is invalid", "Invitation code is invalid": "کد دعوت نامعتبر است",
"Invitation code suspended": "Invitation code suspended", "Invitation code suspended": "کد دعوت معلق است",
"LDAP user name or password incorrect": "LDAP user name or password incorrect", "LDAP user name or password incorrect": "نام کاربری یا رمز عبور LDAP نادرست است",
"LastName cannot be blank": "LastName cannot be blank", "LastName cannot be blank": "نام خانوادگی نمی‌تواند خالی باشد",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server", "Multiple accounts with same uid, please check your ldap server": "چندین حساب با uid یکسان، لطفاً سرور LDAP خود را بررسی کنید",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "سازمان وجود ندارد",
"Phone already exists": "Phone already exists", "Phone already exists": "تلفن قبلاً وجود دارد",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "تلفن نمی‌تواند خالی باشد",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "شماره تلفن نامعتبر است",
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code", "Please register using the email corresponding to the invitation code": "لطفاً با استفاده از ایمیل مربوط به کد دعوت ثبت‌نام کنید",
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code", "Please register using the phone corresponding to the invitation code": "لطفاً با استفاده از تلفن مربوط به کد دعوت ثبت‌نام کنید",
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code", "Please register using the username corresponding to the invitation code": "لطفاً با استفاده از نام کاربری مربوط به کد دعوت ثبت‌نام کنید",
"Session outdated, please login again": "Session outdated, please login again", "Session outdated, please login again": "جلسه منقضی شده است، لطفاً دوباره وارد شوید",
"The invitation code has already been used": "The invitation code has already been used", "The invitation code has already been used": "کد دعوت قبلاً استفاده شده است",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "ورود کاربر ممنوع است، لطفاً با مدیر تماس بگیرید",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server", "The user: %s doesn't exist in LDAP server": "کاربر: %s در سرور LDAP وجود ندارد",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "نام کاربری فقط می‌تواند حاوی کاراکترهای الفبایی عددی، زیرخط یا خط تیره باشد، نمی‌تواند خط تیره یا زیرخط متوالی داشته باشد، و نمی‌تواند با خط تیره یا زیرخط شروع یا پایان یابد.",
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex", "The value \"%s\" for account field \"%s\" doesn't match the account item regex": "مقدار \"%s\" برای فیلد حساب \"%s\" با عبارت منظم مورد حساب مطابقت ندارد",
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"", "The value \"%s\" for signup field \"%s\" doesn't match the signup item regex of the application \"%s\"": "مقدار \"%s\" برای فیلد ثبت‌نام \"%s\" با عبارت منظم مورد ثبت‌نام برنامه \"%s\" مطابقت ندارد",
"Username already exists": "Username already exists", "Username already exists": "نام کاربری قبلاً وجود دارد",
"Username cannot be an email address": "Username cannot be an email address", "Username cannot be an email address": "نام کاربری نمی‌تواند یک آدرس ایمیل باشد",
"Username cannot contain white spaces": "Username cannot contain white spaces", "Username cannot contain white spaces": "نام کاربری نمی‌تواند حاوی فاصله باشد",
"Username cannot start with a digit": "Username cannot start with a digit", "Username cannot start with a digit": "نام کاربری نمی‌تواند با یک رقم شروع شود",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).", "Username is too long (maximum is 39 characters).": "نام کاربری بیش از حد طولانی است (حداکثر ۳۹ کاراکتر).",
"Username must have at least 2 characters": "Username must have at least 2 characters", "Username must have at least 2 characters": "نام کاربری باید حداقل ۲ کاراکتر داشته باشد",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "شما رمز عبور یا کد اشتباه را بیش از حد وارد کرده‌اید، لطفاً %d دقیقه صبر کنید و دوباره تلاش کنید",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone", "Your region is not allow to signup by phone": "منطقه شما اجازه ثبت‌نام با تلفن را ندارد",
"password or code is incorrect": "password or code is incorrect", "password or code is incorrect": "رمز عبور یا کد نادرست است",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances", "password or code is incorrect, you have %d remaining chances": "رمز عبور یا کد نادرست است، شما %d فرصت باقی‌مانده دارید",
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "نوع رمز عبور پشتیبانی نشده: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter", "Missing parameter": "پارامتر گمشده",
"Please login first": "Please login first", "Please login first": "لطفاً ابتدا وارد شوید",
"The organization: %s should have one application at least": "The organization: %s should have one application at least", "The organization: %s should have one application at least": "سازمان: %s باید حداقل یک برنامه داشته باشد",
"The user: %s doesn't exist": "The user: %s doesn't exist", "The user: %s doesn't exist": "کاربر: %s وجود ندارد",
"don't support captchaProvider: ": "don't support captchaProvider: ", "don't support captchaProvider: ": "از captchaProvider پشتیبانی نمی‌شود: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode", "this operation is not allowed in demo mode": "این عملیات در حالت دمو مجاز نیست",
"this operation requires administrator to perform": "this operation requires administrator to perform" "this operation requires administrator to perform": "این عملیات نیاز به مدیر برای انجام دارد"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist" "Ldap server exist": "سرور LDAP وجود دارد"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "لطفاً ابتدا پیوند دهید",
"This application has no providers": "This application has no providers", "This application has no providers": "این برنامه ارائه‌دهنده‌ای ندارد",
"This application has no providers of type": "This application has no providers of type", "This application has no providers of type": "این برنامه ارائه‌دهنده‌ای از نوع ندارد",
"This provider can't be unlinked": "This provider can't be unlinked", "This provider can't be unlinked": "این ارائه‌دهنده نمی‌تواند لغو پیوند شود",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users", "You are not the global admin, you can't unlink other users": "شما مدیر جهانی نیستید، نمی‌توانید کاربران دیگر را لغو پیوند کنید",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application" "You can't unlink yourself, you are not a member of any application": "شما نمی‌توانید خودتان را لغو پیوند کنید، شما عضو هیچ برنامه‌ای نیستید"
}, },
"organization": { "organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.", "Only admin can modify the %s.": "فقط مدیر می‌تواند %s را تغییر دهد.",
"The %s is immutable.": "The %s is immutable.", "The %s is immutable.": "%s غیرقابل تغییر است.",
"Unknown modify rule %s.": "Unknown modify rule %s." "Unknown modify rule %s.": "قانون تغییر ناشناخته %s."
}, },
"permission": { "permission": {
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist" "The permission: \"%s\" doesn't exist": "مجوز: \"%s\" وجود ندارد"
}, },
"provider": { "provider": {
"Invalid application id": "Invalid application id", "Invalid application id": "شناسه برنامه نامعتبر",
"the provider: %s does not exist": "the provider: %s does not exist" "the provider: %s does not exist": "ارائه‌دهنده: %s وجود ندارد"
}, },
"resource": { "resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar", "User is nil for tag: avatar": "کاربر برای برچسب: آواتار تهی است",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s" "Username or fullFilePath is empty: username = %s, fullFilePath = %s": "نام کاربری یا مسیر کامل فایل خالی است: نام کاربری = %s، مسیر کامل فایل = %s"
}, },
"saml": { "saml": {
"Application %s not found": "Application %s not found" "Application %s not found": "برنامه %s یافت نشد"
}, },
"saml_sp": { "saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML" "provider %s's category is not SAML": "دسته‌بندی ارائه‌دهنده %s SAML نیست"
}, },
"service": { "service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v", "Empty parameters for emailForm: %v": "پارامترهای خالی برای emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s", "Invalid Email receivers: %s": "گیرندگان ایمیل نامعتبر: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s" "Invalid phone receivers: %s": "گیرندگان تلفن نامعتبر: %s"
}, },
"storage": { "storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed", "The objectKey: %s is not allowed": "objectKey: %s مجاز نیست",
"The provider type: %s is not supported": "The provider type: %s is not supported" "The provider type: %s is not supported": "نوع ارائه‌دهنده: %s پشتیبانی نمی‌شود"
}, },
"token": { "token": {
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "grant_type: %s در این برنامه پشتیبانی نمی‌شود",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "برنامه نامعتبر یا clientSecret نادرست",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "client_id نامعتبر",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list", "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "آدرس بازگشت: %s در لیست آدرس‌های بازگشت مجاز وجود ندارد",
"Token not found, invalid accessToken": "Token not found, invalid accessToken" "Token not found, invalid accessToken": "توکن یافت نشد، accessToken نامعتبر"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "نام نمایشی نمی‌تواند خالی باشد",
"New password cannot contain blank space.": "New password cannot contain blank space." "New password cannot contain blank space.": "رمز عبور جدید نمی‌تواند حاوی فاصله خالی باشد."
}, },
"user_upload": { "user_upload": {
"Failed to import users": "Failed to import users" "Failed to import users": "عدم موفقیت در وارد کردن کاربران"
}, },
"util": { "util": {
"No application is found for userId: %s": "No application is found for userId: %s", "No application is found for userId: %s": "هیچ برنامه‌ای برای userId: %s یافت نشد",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s", "No provider for category: %s is found for application: %s": "هیچ ارائه‌دهنده‌ای برای دسته‌بندی: %s برای برنامه: %s یافت نشد",
"The provider: %s is not found": "The provider: %s is not found" "The provider: %s is not found": "ارائه‌دهنده: %s یافت نشد"
}, },
"verification": { "verification": {
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "ارائه‌دهنده کپچا نامعتبر.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s", "Phone number is invalid in your region %s": "شماره تلفن در منطقه شما نامعتبر است %s",
"The verification code has not been sent yet!": "The verification code has not been sent yet!", "The verification code has not been sent yet!": "کد تأیید هنوز ارسال نشده است!",
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!", "The verification code has not been sent yet, or has already been used!": "کد تأیید هنوز ارسال نشده است، یا قبلاً استفاده شده است!",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "تست تورینگ ناموفق بود.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.", "Unable to get the email modify rule.": "عدم توانایی در دریافت قانون تغییر ایمیل.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.", "Unable to get the phone modify rule.": "عدم توانایی در دریافت قانون تغییر تلفن.",
"Unknown type": "Unknown type", "Unknown type": "نوع ناشناخته",
"Wrong verification code!": "Wrong verification code!", "Wrong verification code!": "کد تأیید اشتباه!",
"You should verify your code in %d min!": "You should verify your code in %d min!", "You should verify your code in %d min!": "شما باید کد خود را در %d دقیقه تأیید کنید!",
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s", "please add a SMS provider to the \"Providers\" list for the application: %s": "لطفاً یک ارائه‌دهنده پیامک به لیست \"ارائه‌دهندگان\" برای برنامه: %s اضافه کنید",
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s", "please add an Email provider to the \"Providers\" list for the application: %s": "لطفاً یک ارائه‌دهنده ایمیل به لیست \"ارائه‌دهندگان\" برای برنامه: %s اضافه کنید",
"the user does not exist, please sign up first": "the user does not exist, please sign up first" "the user does not exist, please sign up first": "کاربر وجود ندارد، لطفاً ابتدا ثبت‌نام کنید"
}, },
"webauthn": { "webauthn": {
"Found no credentials for this user": "Found no credentials for this user", "Found no credentials for this user": "هیچ اعتباری برای این کاربر یافت نشد",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first" "Please call WebAuthnSigninBegin first": "لطفاً ابتدا WebAuthnSigninBegin را فراخوانی کنید"
} }
} }

View File

@ -15,10 +15,10 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT", "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
"The application: %s does not exist": "Приложение: %s не существует", "The application: %s does not exist": "Приложение: %s не существует",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application", "The login method: login with LDAP is not enabled for the application": "Метод входа в систему: вход с помощью LDAP не включен для приложения",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application", "The login method: login with SMS is not enabled for the application": "Метод входа: вход с помощью SMS не включен для приложения",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application", "The login method: login with email is not enabled for the application": "Метод входа: вход с помощью электронной почты не включен для приложения",
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application", "The login method: login with face is not enabled for the application": "Метод входа: вход с помощью лица не включен для приложения",
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения", "The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
"The organization: %s does not exist": "The organization: %s does not exist", "The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения", "The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
@ -53,16 +53,16 @@
"Phone already exists": "Телефон уже существует", "Phone already exists": "Телефон уже существует",
"Phone cannot be empty": "Телефон не может быть пустым", "Phone cannot be empty": "Телефон не может быть пустым",
"Phone number is invalid": "Номер телефона является недействительным", "Phone number is invalid": "Номер телефона является недействительным",
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code", "Please register using the email corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя электронную почту, соответствующую коду приглашения",
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code", "Please register using the phone corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь по телефону, соответствующему коду приглашения",
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code", "Please register using the username corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя имя пользователя, соответствующее коду приглашения",
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова", "Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
"The invitation code has already been used": "The invitation code has already been used", "The invitation code has already been used": "The invitation code has already been used",
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору", "The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
"The user: %s doesn't exist in LDAP server": "Пользователь %s не существует на LDAP сервере", "The user: %s doesn't exist in LDAP server": "Пользователь %s не существует на LDAP сервере",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex", "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Значение \\\"%s\\\" для поля аккаунта \\\"%s\\\" не соответствует регулярному значению",
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"", "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Значение \\\"%s\\\" поля регистрации \\\"%s\\\" не соответствует регулярному выражению приложения \\\"%s\\\"",
"Username already exists": "Имя пользователя уже существует", "Username already exists": "Имя пользователя уже существует",
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты", "Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы", "Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
@ -78,11 +78,11 @@
"general": { "general": {
"Missing parameter": "Отсутствующий параметр", "Missing parameter": "Отсутствующий параметр",
"Please login first": "Пожалуйста, сначала войдите в систему", "Please login first": "Пожалуйста, сначала войдите в систему",
"The organization: %s should have one application at least": "The organization: %s should have one application at least", "The organization: %s should have one application at least": "Организация: %s должна иметь хотя бы одно приложение",
"The user: %s doesn't exist": "Пользователь %s не существует", "The user: %s doesn't exist": "Пользователь %s не существует",
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ", "don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
"this operation is not allowed in demo mode": "эта операция не разрешена в демо-режиме", "this operation is not allowed in demo mode": "эта операция не разрешена в демо-режиме",
"this operation requires administrator to perform": "this operation requires administrator to perform" "this operation requires administrator to perform": "для выполнения этой операции требуется администратор"
}, },
"ldap": { "ldap": {
"Ldap server exist": "LDAP-сервер существует" "Ldap server exist": "LDAP-сервер существует"
@ -101,11 +101,11 @@
"Unknown modify rule %s.": "Неизвестное изменение правила %s." "Unknown modify rule %s.": "Неизвестное изменение правила %s."
}, },
"permission": { "permission": {
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist" "The permission: \\\"%s\\\" doesn't exist": "Разрешение: \\\"%s\\\" не существует"
}, },
"provider": { "provider": {
"Invalid application id": "Неверный идентификатор приложения", "Invalid application id": "Неверный идентификатор приложения",
"the provider: %s does not exist": "провайдер: %s не существует" "the provider: %s does not exist": "Провайдер: %s не существует"
}, },
"resource": { "resource": {
"User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар", "User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар",
@ -115,7 +115,7 @@
"Application %s not found": "Приложение %s не найдено" "Application %s not found": "Приложение %s не найдено"
}, },
"saml_sp": { "saml_sp": {
"provider %s's category is not SAML": "категория провайдера %s не является SAML" "provider %s's category is not SAML": "Категория провайдера %s не является SAML"
}, },
"service": { "service": {
"Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v", "Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v",
@ -148,7 +148,7 @@
"verification": { "verification": {
"Invalid captcha provider.": "Недействительный поставщик CAPTCHA.", "Invalid captcha provider.": "Недействительный поставщик CAPTCHA.",
"Phone number is invalid in your region %s": "Номер телефона недействителен в вашем регионе %s", "Phone number is invalid in your region %s": "Номер телефона недействителен в вашем регионе %s",
"The verification code has not been sent yet!": "The verification code has not been sent yet!", "The verification code has not been sent yet!": "Код проверки еще не отправлен!",
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!", "The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!",
"Turing test failed.": "Тест Тьюринга не удался.", "Turing test failed.": "Тест Тьюринга не удался.",
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.", "Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
@ -156,8 +156,8 @@
"Unknown type": "Неизвестный тип", "Unknown type": "Неизвестный тип",
"Wrong verification code!": "Неправильный код подтверждения!", "Wrong verification code!": "Неправильный код подтверждения!",
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!", "You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s", "please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "Пожалуйста, добавьте поставщика SMS в список \\\"Провайдеры\\\" для приложения: %s",
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s", "please add an Email provider to the \\\"Providers\\\" list for the application: %s": "Пожалуйста, добавьте поставщика электронной почты в список \\\"Провайдеры\\\" для приложения: %s",
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь" "the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
}, },
"webauthn": { "webauthn": {

View File

@ -188,10 +188,23 @@ type GitHubUserInfo struct {
} `json:"plan"` } `json:"plan"`
} }
type GitHubUserEmailInfo struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
Visibility string `json:"visibility"`
}
type GitHubErrorInfo struct {
Message string `json:"message"`
DocumentationUrl string `json:"documentation_url"`
Status string `json:"status"`
}
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req, err := http.NewRequest("GET", "https://api.github.com/user", nil) req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil { if err != nil {
panic(err) return nil, err
} }
req.Header.Add("Authorization", "token "+token.AccessToken) req.Header.Add("Authorization", "token "+token.AccessToken)
resp, err := idp.Client.Do(req) resp, err := idp.Client.Do(req)
@ -212,6 +225,42 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err return nil, err
} }
if githubUserInfo.Email == "" {
reqEmail, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil)
if err != nil {
return nil, err
}
reqEmail.Header.Add("Authorization", "token "+token.AccessToken)
respEmail, err := idp.Client.Do(reqEmail)
if err != nil {
return nil, err
}
defer respEmail.Body.Close()
emailBody, err := io.ReadAll(respEmail.Body)
if err != nil {
return nil, err
}
if respEmail.StatusCode != 200 {
var errMessage GitHubErrorInfo
err = json.Unmarshal(emailBody, &errMessage)
if err != nil {
return nil, err
}
fmt.Printf("GithubIdProvider:GetUserInfo() error, status code = %d, error message = %v\n", respEmail.StatusCode, errMessage)
} else {
var userEmails []GitHubUserEmailInfo
err = json.Unmarshal(emailBody, &userEmails)
if err != nil {
return nil, err
}
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
}
}
userInfo := UserInfo{ userInfo := UserInfo{
Id: strconv.Itoa(githubUserInfo.Id), Id: strconv.Itoa(githubUserInfo.Id),
Username: githubUserInfo.Login, Username: githubUserInfo.Login,
@ -248,3 +297,27 @@ func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte,
return data, nil return data, nil
} }
func (idp *GithubIdProvider) getEmailFromEmailsResult(emailInfo []GitHubUserEmailInfo) string {
primaryEmail := ""
verifiedEmail := ""
for _, addr := range emailInfo {
if !addr.Verified || strings.Contains(addr.Email, "users.noreply.github.com") {
continue
}
if addr.Primary {
primaryEmail = addr.Email
break
} else if verifiedEmail == "" {
verifiedEmail = addr.Email
}
}
if primaryEmail != "" {
return primaryEmail
}
return verifiedEmail
}

161
idp/kwai.go Normal file
View File

@ -0,0 +1,161 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"golang.org/x/oauth2"
)
type KwaiIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewKwaiIdProvider(clientId string, clientSecret string, redirectUrl string) *KwaiIdProvider {
idp := &KwaiIdProvider{}
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
return idp
}
func (idp *KwaiIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *KwaiIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
endpoint := oauth2.Endpoint{
TokenURL: "https://open.kuaishou.com/oauth2/access_token",
AuthURL: "https://open.kuaishou.com/oauth2/authorize", // qr code: /oauth2/connect
}
config := &oauth2.Config{
Scopes: []string{"user_info"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type KwaiTokenResp struct {
Result int `json:"result"`
ErrorMsg string `json:"error_msg"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
OpenId string `json:"open_id"`
Scopes []string `json:"scopes"`
}
// GetToken use code to get access_token
func (idp *KwaiIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := map[string]string{
"app_id": idp.Config.ClientID,
"app_secret": idp.Config.ClientSecret,
"code": code,
"grant_type": "authorization_code",
}
tokenUrl := fmt.Sprintf("%s?app_id=%s&app_secret=%s&code=%s&grant_type=authorization_code",
idp.Config.Endpoint.TokenURL, params["app_id"], params["app_secret"], params["code"])
resp, err := idp.Client.Get(tokenUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var tokenResp KwaiTokenResp
err = json.Unmarshal(body, &tokenResp)
if err != nil {
return nil, err
}
if tokenResp.Result != 1 {
return nil, fmt.Errorf("get token error: %s", tokenResp.ErrorMsg)
}
token := &oauth2.Token{
AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken,
Expiry: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
}
raw := make(map[string]interface{})
raw["open_id"] = tokenResp.OpenId
token = token.WithExtra(raw)
return token, nil
}
// More details: https://open.kuaishou.com/openapi/user_info
type KwaiUserInfo struct {
Result int `json:"result"`
ErrorMsg string `json:"error_msg"`
UserInfo struct {
Head string `json:"head"`
Name string `json:"name"`
Sex string `json:"sex"`
City string `json:"city"`
} `json:"user_info"`
}
// GetUserInfo use token to get user profile
func (idp *KwaiIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
userInfoUrl := fmt.Sprintf("https://open.kuaishou.com/openapi/user_info?app_id=%s&access_token=%s",
idp.Config.ClientID, token.AccessToken)
resp, err := idp.Client.Get(userInfoUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var kwaiUserInfo KwaiUserInfo
err = json.Unmarshal(body, &kwaiUserInfo)
if err != nil {
return nil, err
}
if kwaiUserInfo.Result != 1 {
return nil, fmt.Errorf("get user info error: %s", kwaiUserInfo.ErrorMsg)
}
userInfo := &UserInfo{
Id: token.Extra("open_id").(string),
Username: kwaiUserInfo.UserInfo.Name,
DisplayName: kwaiUserInfo.UserInfo.Name,
AvatarUrl: kwaiUserInfo.UserInfo.Head,
Extra: map[string]string{
"gender": kwaiUserInfo.UserInfo.Sex,
"city": kwaiUserInfo.UserInfo.City,
},
}
return userInfo, nil
}

View File

@ -113,6 +113,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Douyin": case "Douyin":
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Kwai":
return NewKwaiIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Bilibili": case "Bilibili":
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "MetaMask": case "MetaMask":

View File

@ -15,6 +15,7 @@
package ldap package ldap
import ( import (
"crypto/tls"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"log" "log"
@ -27,21 +28,68 @@ import (
func StartLdapServer() { func StartLdapServer() {
ldapServerPort := conf.GetConfigString("ldapServerPort") ldapServerPort := conf.GetConfigString("ldapServerPort")
if ldapServerPort == "" || ldapServerPort == "0" { ldapsServerPort := conf.GetConfigString("ldapsServerPort")
return
}
server := ldap.NewServer() server := ldap.NewServer()
serverSsl := ldap.NewServer()
routes := ldap.NewRouteMux() routes := ldap.NewRouteMux()
routes.Bind(handleBind) routes.Bind(handleBind)
routes.Search(handleSearch).Label(" SEARCH****") routes.Search(handleSearch).Label(" SEARCH****")
server.Handle(routes) server.Handle(routes)
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort) serverSsl.Handle(routes)
go func() {
if ldapServerPort == "" || ldapServerPort == "0" {
return
}
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
if err != nil {
log.Printf("StartLdapServer() failed, err = %s", err.Error())
}
}()
go func() {
if ldapsServerPort == "" || ldapsServerPort == "0" {
return
}
ldapsCertId := conf.GetConfigString("ldapsCertId")
if ldapsCertId == "" {
return
}
config, err := getTLSconfig(ldapsCertId)
if err != nil {
log.Printf("StartLdapsServer() failed, err = %s", err.Error())
return
}
secureConn := func(s *ldap.Server) {
s.Listener = tls.NewListener(s.Listener, config)
}
err = serverSsl.ListenAndServe("0.0.0.0:"+ldapsServerPort, secureConn)
if err != nil {
log.Printf("StartLdapsServer() failed, err = %s", err.Error())
}
}()
}
func getTLSconfig(ldapsCertId string) (*tls.Config, error) {
rawCert, err := object.GetCert(ldapsCertId)
if err != nil { if err != nil {
log.Printf("StartLdapServer() failed, err = %s", err.Error()) return nil, err
} }
if rawCert == nil {
return nil, fmt.Errorf("cert is empty")
}
cert, err := tls.X509KeyPair([]byte(rawCert.Certificate), []byte(rawCert.PrivateKey))
if err != nil {
return &tls.Config{}, err
}
return &tls.Config{
MinVersion: tls.VersionTLS10,
MaxVersion: tls.VersionTLS13,
Certificates: []tls.Certificate{cert},
}, nil
} }
func handleBind(w ldap.ResponseWriter, m *ldap.Message) { func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
@ -142,7 +190,7 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
} }
for _, attr := range attrs { for _, attr := range attrs {
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user)) e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "cn" { if string(attr) == "title" {
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user)) e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
} }
} }

View File

@ -83,6 +83,11 @@ func main() {
// logs.SetLevel(logs.LevelInformational) // logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false) logs.SetLogFuncCall(false)
err = util.StopOldInstance(port)
if err != nil {
panic(err)
}
go ldap.StartLdapServer() go ldap.StartLdapServer()
go radius.StartRadiusServer() go radius.StartRadiusServer()
go object.ClearThroughputPerSecond() go object.ClearThroughputPerSecond()

29
notification/cucloud.go Normal file
View File

@ -0,0 +1,29 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/cucloud"
)
func NewCucloudProvider(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType string) (notify.Notifier, error) {
cucloud := cucloud.New(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType)
notifier := notify.New()
notifier.UseServices(cucloud)
return notifier, nil
}

View File

@ -16,7 +16,7 @@ package notification
import "github.com/casdoor/notify" import "github.com/casdoor/notify"
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) { func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string, regionId string) (notify.Notifier, error) {
if typ == "Telegram" { if typ == "Telegram" {
return NewTelegramProvider(clientSecret, receiver) return NewTelegramProvider(clientSecret, receiver)
} else if typ == "Custom HTTP" { } else if typ == "Custom HTTP" {
@ -53,6 +53,8 @@ func GetNotificationProvider(typ string, clientId string, clientSecret string, c
return NewRocketChatProvider(clientId, clientSecret, appId, receiver) return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
} else if typ == "Viber" { } else if typ == "Viber" {
return NewViberProvider(clientId, clientSecret, appId, receiver) return NewViberProvider(clientId, clientSecret, appId, receiver)
} else if typ == "CUCloud" {
return NewCucloudProvider(clientId, clientSecret, appId, title, regionId, clientId2, metaData)
} }
return nil, nil return nil, nil

View File

@ -723,8 +723,15 @@ func (application *Application) GetId() string {
} }
func (application *Application) IsRedirectUriValid(redirectUri string) bool { func (application *Application) IsRedirectUriValid(redirectUri string) bool {
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app", ".chromiumapp.org"}, application.RedirectUris...) isValid, err := util.IsValidOrigin(redirectUri)
for _, targetUri := range redirectUris { if err != nil {
panic(err)
}
if isValid {
return true
}
for _, targetUri := range application.RedirectUris {
targetUriRegex := regexp.MustCompile(targetUri) targetUriRegex := regexp.MustCompile(targetUri)
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) { if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
return true return true

View File

@ -273,7 +273,7 @@ func CheckPasswordComplexity(user *User, password string) string {
return CheckPasswordComplexityByOrg(organization, password) return CheckPasswordComplexityByOrg(organization, password)
} }
func checkLdapUserPassword(user *User, password string, lang string) error { func CheckLdapUserPassword(user *User, password string, lang string) error {
ldaps, err := GetLdaps(user.Owner) ldaps, err := GetLdaps(user.Owner)
if err != nil { if err != nil {
return err return err
@ -368,7 +368,7 @@ func CheckUserPassword(organization string, username string, password string, la
} }
// only for LDAP users // only for LDAP users
err = checkLdapUserPassword(user, password, lang) err = CheckLdapUserPassword(user, password, lang)
if err != nil { if err != nil {
if err.Error() == "user not exist" { if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username) return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
@ -381,7 +381,13 @@ func CheckUserPassword(organization string, username string, password string, la
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = checkPasswordExpired(user, lang)
if err != nil {
return nil, err
}
} }
return user, nil return user, nil
} }
@ -520,11 +526,46 @@ func CheckUsername(username string, lang string) string {
return "" return ""
} }
func CheckUsernameWithEmail(username string, lang string) string {
if username == "" {
return i18n.Translate(lang, "check:Empty username.")
} else if len(username) > 39 {
return i18n.Translate(lang, "check:Username is too long (maximum is 39 characters).")
}
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
if !util.ReUserNameWithEmail.MatchString(username) {
return i18n.Translate(lang, "check:Username supports email format. Also The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline. Also pay attention to the email format.")
}
return ""
}
func CheckUpdateUser(oldUser, user *User, lang string) string { func CheckUpdateUser(oldUser, user *User, lang string) string {
if oldUser.Name != user.Name { if oldUser.Name != user.Name {
if msg := CheckUsername(user.Name, lang); msg != "" { organizationName := oldUser.Owner
return msg if organizationName == "" {
organizationName = user.Owner
} }
organization, err := getOrganization("admin", organizationName)
if err != nil {
return err.Error()
}
if organization == nil {
return fmt.Sprintf(i18n.Translate(lang, "auth:The organization: %s does not exist"), organizationName)
}
if organization.UseEmailAsUsername {
if msg := CheckUsernameWithEmail(user.Name, lang); msg != "" {
return msg
}
} else {
if msg := CheckUsername(user.Name, lang); msg != "" {
return msg
}
}
if HasUserByField(user.Owner, "name", user.Name) { if HasUserByField(user.Owner, "name", user.Name) {
return i18n.Translate(lang, "check:Username already exists") return i18n.Translate(lang, "check:Username already exists")
} }

View File

@ -43,6 +43,8 @@ func CheckEntryIp(clientIp string, user *User, application *Application, organiz
if err != nil { if err != nil {
application.IpRestriction = err.Error() + application.Name application.IpRestriction = err.Error() + application.Name
return fmt.Errorf(err.Error() + application.Name) return fmt.Errorf(err.Error() + application.Name)
} else {
application.IpRestriction = ""
} }
if organization == nil && application.OrganizationObj != nil { if organization == nil && application.OrganizationObj != nil {
@ -55,6 +57,8 @@ func CheckEntryIp(clientIp string, user *User, application *Application, organiz
if err != nil { if err != nil {
organization.IpRestriction = err.Error() + organization.Name organization.IpRestriction = err.Error() + organization.Name
return fmt.Errorf(err.Error() + organization.Name) return fmt.Errorf(err.Error() + organization.Name)
} else {
organization.IpRestriction = ""
} }
} }

View File

@ -0,0 +1,53 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"time"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
)
func checkPasswordExpired(user *User, lang string) error {
organization, err := GetOrganizationByUser(user)
if err != nil {
return err
}
if organization == nil {
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
}
passwordExpireDays := organization.PasswordExpireDays
if passwordExpireDays <= 0 {
return nil
}
lastChangePasswordTime := user.LastChangePasswordTime
if lastChangePasswordTime == "" {
if user.CreatedTime == "" {
return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\""))
}
lastChangePasswordTime = user.CreatedTime
}
lastTime := util.String2Time(lastChangePasswordTime)
expireTime := lastTime.AddDate(0, 0, passwordExpireDays)
if time.Now().After(expireTime) {
return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\""))
}
return nil
}

View File

@ -19,124 +19,90 @@ import (
"time" "time"
) )
type Dashboard struct { type DashboardDateItem struct {
OrganizationCounts []int `json:"organizationCounts"` CreatedTime string `json:"createTime"`
UserCounts []int `json:"userCounts"`
ProviderCounts []int `json:"providerCounts"`
ApplicationCounts []int `json:"applicationCounts"`
SubscriptionCounts []int `json:"subscriptionCounts"`
} }
func GetDashboard(owner string) (*Dashboard, error) { type DashboardMapItem struct {
dashboardDateItems []DashboardDateItem
itemCount int64
}
func GetDashboard(owner string) (*map[string][]int64, error) {
if owner == "All" { if owner == "All" {
owner = "" owner = ""
} }
dashboard := &Dashboard{ dashboard := make(map[string][]int64)
OrganizationCounts: make([]int, 31), dashboardMap := sync.Map{}
UserCounts: make([]int, 31), tableNames := []string{"organization", "user", "provider", "application", "subscription", "role", "group", "resource", "cert", "permission", "transaction", "model", "adapter", "enforcer"}
ProviderCounts: make([]int, 31),
ApplicationCounts: make([]int, 31), time30day := time.Now().AddDate(0, 0, -30)
SubscriptionCounts: make([]int, 31), var wg sync.WaitGroup
var err error
wg.Add(len(tableNames))
ch := make(chan error, len(tableNames))
for _, tableName := range tableNames {
dashboard[tableName+"Counts"] = make([]int64, 31)
tableName := tableName
go func(ch chan error) {
defer wg.Done()
dashboardDateItems := []DashboardDateItem{}
var countResult int64
dbQueryBefore := ormer.Engine.Cols("created_time")
dbQueryAfter := ormer.Engine.Cols("created_time")
if owner != "" {
dbQueryAfter = dbQueryAfter.And("owner = ?", owner)
dbQueryBefore = dbQueryBefore.And("owner = ?", owner)
}
if countResult, err = dbQueryBefore.And("created_time < ?", time30day).Table(tableName).Count(); err != nil {
ch <- err
return
}
if err = dbQueryAfter.And("created_time >= ?", time30day).Table(tableName).Find(&dashboardDateItems); err != nil {
ch <- err
return
}
dashboardMap.Store(tableName, DashboardMapItem{
dashboardDateItems: dashboardDateItems,
itemCount: countResult,
})
}(ch)
} }
organizations := []Organization{}
users := []User{}
providers := []Provider{}
applications := []Application{}
subscriptions := []Subscription{}
var wg sync.WaitGroup
wg.Add(5)
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
panic(err)
}
}()
wg.Wait() wg.Wait()
close(ch)
for err = range ch {
if err != nil {
return nil, err
}
}
nowTime := time.Now() nowTime := time.Now()
for i := 30; i >= 0; i-- { for i := 30; i >= 0; i-- {
cutTime := nowTime.AddDate(0, 0, -i) cutTime := nowTime.AddDate(0, 0, -i)
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime) for _, tableName := range tableNames {
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime) item, exist := dashboardMap.Load(tableName)
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime) if !exist {
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime) continue
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime) }
dashboard[tableName+"Counts"][30-i] = countCreatedBefore(item.(DashboardMapItem), cutTime)
}
} }
return dashboard, nil return &dashboard, nil
} }
func countCreatedBefore(objects interface{}, before time.Time) int { func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 {
count := 0 count := dashboardMapItem.itemCount
switch obj := objects.(type) { for _, e := range dashboardMapItem.dashboardDateItems {
case []Organization: createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
for _, o := range obj { if createdTime.Before(before) {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime) count++
if createdTime.Before(before) {
count++
}
}
case []User:
for _, u := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Provider:
for _, p := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Application:
for _, a := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Subscription:
for _, s := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
if createdTime.Before(before) {
count++
}
} }
} }
return count return count

View File

@ -48,12 +48,16 @@ type InitData struct {
Transactions []*Transaction `json:"transactions"` Transactions []*Transaction `json:"transactions"`
} }
var initDataNewOnly bool
func InitFromFile() { func InitFromFile() {
initDataFile := conf.GetConfigString("initDataFile") initDataFile := conf.GetConfigString("initDataFile")
if initDataFile == "" { if initDataFile == "" {
return return
} }
initDataNewOnly = conf.GetConfigBool("initDataNewOnly")
initData, err := readInitDataFromFile(initDataFile) initData, err := readInitDataFromFile(initDataFile)
if err != nil { if err != nil {
panic(err) panic(err)
@ -269,6 +273,9 @@ func initDefinedOrganization(organization *Organization) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteOrganization(organization) affected, err := deleteOrganization(organization)
if err != nil { if err != nil {
panic(err) panic(err)
@ -295,6 +302,9 @@ func initDefinedApplication(application *Application) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteApplication(application) affected, err := deleteApplication(application)
if err != nil { if err != nil {
panic(err) panic(err)
@ -316,6 +326,9 @@ func initDefinedUser(user *User) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteUser(user) affected, err := deleteUser(user)
if err != nil { if err != nil {
panic(err) panic(err)
@ -342,6 +355,9 @@ func initDefinedCert(cert *Cert) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteCert(cert) affected, err := DeleteCert(cert)
if err != nil { if err != nil {
panic(err) panic(err)
@ -364,6 +380,9 @@ func initDefinedLdap(ldap *Ldap) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteLdap(ldap) affected, err := DeleteLdap(ldap)
if err != nil { if err != nil {
panic(err) panic(err)
@ -385,6 +404,9 @@ func initDefinedProvider(provider *Provider) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteProvider(provider) affected, err := DeleteProvider(provider)
if err != nil { if err != nil {
panic(err) panic(err)
@ -406,6 +428,9 @@ func initDefinedModel(model *Model) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteModel(model) affected, err := DeleteModel(model)
if err != nil { if err != nil {
panic(err) panic(err)
@ -428,6 +453,9 @@ func initDefinedPermission(permission *Permission) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := deletePermission(permission) affected, err := deletePermission(permission)
if err != nil { if err != nil {
panic(err) panic(err)
@ -450,6 +478,9 @@ func initDefinedPayment(payment *Payment) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeletePayment(payment) affected, err := DeletePayment(payment)
if err != nil { if err != nil {
panic(err) panic(err)
@ -472,6 +503,9 @@ func initDefinedProduct(product *Product) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteProduct(product) affected, err := DeleteProduct(product)
if err != nil { if err != nil {
panic(err) panic(err)
@ -494,6 +528,9 @@ func initDefinedResource(resource *Resource) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteResource(resource) affected, err := DeleteResource(resource)
if err != nil { if err != nil {
panic(err) panic(err)
@ -516,6 +553,9 @@ func initDefinedRole(role *Role) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteRole(role) affected, err := deleteRole(role)
if err != nil { if err != nil {
panic(err) panic(err)
@ -538,6 +578,9 @@ func initDefinedSyncer(syncer *Syncer) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteSyncer(syncer) affected, err := DeleteSyncer(syncer)
if err != nil { if err != nil {
panic(err) panic(err)
@ -560,6 +603,9 @@ func initDefinedToken(token *Token) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteToken(token) affected, err := DeleteToken(token)
if err != nil { if err != nil {
panic(err) panic(err)
@ -582,6 +628,9 @@ func initDefinedWebhook(webhook *Webhook) {
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteWebhook(webhook) affected, err := DeleteWebhook(webhook)
if err != nil { if err != nil {
panic(err) panic(err)
@ -603,6 +652,9 @@ func initDefinedGroup(group *Group) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := deleteGroup(group) affected, err := deleteGroup(group)
if err != nil { if err != nil {
panic(err) panic(err)
@ -624,6 +676,9 @@ func initDefinedAdapter(adapter *Adapter) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteAdapter(adapter) affected, err := DeleteAdapter(adapter)
if err != nil { if err != nil {
panic(err) panic(err)
@ -645,6 +700,9 @@ func initDefinedEnforcer(enforcer *Enforcer) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteEnforcer(enforcer) affected, err := DeleteEnforcer(enforcer)
if err != nil { if err != nil {
panic(err) panic(err)
@ -666,6 +724,9 @@ func initDefinedPlan(plan *Plan) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeletePlan(plan) affected, err := DeletePlan(plan)
if err != nil { if err != nil {
panic(err) panic(err)
@ -687,6 +748,9 @@ func initDefinedPricing(pricing *Pricing) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeletePricing(pricing) affected, err := DeletePricing(pricing)
if err != nil { if err != nil {
panic(err) panic(err)
@ -708,6 +772,9 @@ func initDefinedInvitation(invitation *Invitation) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteInvitation(invitation) affected, err := DeleteInvitation(invitation)
if err != nil { if err != nil {
panic(err) panic(err)
@ -743,6 +810,9 @@ func initDefinedSubscription(subscription *Subscription) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteSubscription(subscription) affected, err := DeleteSubscription(subscription)
if err != nil { if err != nil {
panic(err) panic(err)
@ -764,6 +834,9 @@ func initDefinedTransaction(transaction *Transaction) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
if initDataNewOnly {
return
}
affected, err := DeleteTransaction(transaction) affected, err := DeleteTransaction(transaction)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -20,9 +20,11 @@ import (
"strings" "strings"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr" "github.com/thanhpk/randstr"
"golang.org/x/text/encoding/unicode"
) )
type LdapConn struct { type LdapConn struct {
@ -371,6 +373,64 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
return existUuids, nil return existUuids, nil
} }
func ResetLdapPassword(user *User, newPassword string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err
}
for _, ldapServer := range ldaps {
conn, err := ldapServer.GetLdapConn()
if err != nil {
continue
}
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
0, 0, false, ldapServer.buildAuthFilterString(user), []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
conn.Close()
return err
}
if len(searchResult.Entries) == 0 {
conn.Close()
continue
}
if len(searchResult.Entries) > 1 {
conn.Close()
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
}
userDn := searchResult.Entries[0].DN
var pwdEncoded string
modifyPasswordRequest := goldap.NewModifyRequest(userDn, nil)
if conn.IsAD {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
pwdEncoded, err := utf16.NewEncoder().String("\"" + newPassword + "\"")
if err != nil {
conn.Close()
return err
}
modifyPasswordRequest.Replace("unicodePwd", []string{pwdEncoded})
modifyPasswordRequest.Replace("userAccountControl", []string{"512"})
} else {
pwdEncoded = newPassword
modifyPasswordRequest.Replace("userPassword", []string{pwdEncoded})
}
err = conn.Conn.Modify(modifyPasswordRequest)
if err != nil {
conn.Close()
return err
}
conn.Close()
}
return nil
}
func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) { func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
user := User{} user := User{}
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber) uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)

View File

@ -23,7 +23,7 @@ import (
func getNotificationClient(provider *Provider) (notify.Notifier, error) { func getNotificationClient(provider *Provider) (notify.Notifier, error) {
var client notify.Notifier var client notify.Notifier
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata) client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata, provider.RegionId)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -62,14 +62,15 @@ type Organization struct {
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"` PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
PasswordObfuscatorType string `xorm:"varchar(100)" json:"passwordObfuscatorType"` PasswordObfuscatorType string `xorm:"varchar(100)" json:"passwordObfuscatorType"`
PasswordObfuscatorKey string `xorm:"varchar(100)" json:"passwordObfuscatorKey"` PasswordObfuscatorKey string `xorm:"varchar(100)" json:"passwordObfuscatorKey"`
PasswordExpireDays int `json:"passwordExpireDays"`
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"` CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"` DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"` Languages []string `xorm:"varchar(255)" json:"languages"`
ThemeData *ThemeData `xorm:"json" json:"themeData"` ThemeData *ThemeData `xorm:"json" json:"themeData"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` MasterPassword string `xorm:"varchar(200)" json:"masterPassword"`
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"` DefaultPassword string `xorm:"varchar(200)" json:"defaultPassword"`
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"` MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"` IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
InitScore int `json:"initScore"` InitScore int `json:"initScore"`

View File

@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/beego/beego/context" "github.com/beego/beego/context"
@ -70,6 +71,7 @@ type Provider struct {
IdP string `xorm:"mediumtext" json:"idP"` IdP string `xorm:"mediumtext" json:"idP"`
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"` IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"` EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
EmailRegex string `xorm:"varchar(200)" json:"emailRegex"`
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"` ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
} }
@ -200,6 +202,13 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
return false, nil return false, nil
} }
if provider.EmailRegex != "" {
_, err := regexp.Compile(provider.EmailRegex)
if err != nil {
return false, err
}
}
if name != provider.Name { if name != provider.Name {
err := providerChangeTrigger(name, provider.Name) err := providerChangeTrigger(name, provider.Name)
if err != nil { if err != nil {
@ -234,6 +243,13 @@ func AddProvider(provider *Provider) (bool, error) {
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint) provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
} }
if provider.EmailRegex != "" {
_, err := regexp.Compile(provider.EmailRegex)
if err != nil {
return false, err
}
}
affected, err := ormer.Engine.Insert(provider) affected, err := ormer.Engine.Insert(provider)
if err != nil { if err != nil {
return false, err return false, err
@ -421,7 +437,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
providerInfo.ClientId = provider.ClientId2 providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2 providerInfo.ClientSecret = provider.ClientSecret2
} }
} else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" { } else if provider.Type == "ADFS" || provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "Casdoor" || provider.Type == "Okta" {
providerInfo.HostUrl = provider.Domain providerInfo.HostUrl = provider.Domain
} }

View File

@ -33,7 +33,7 @@ var (
func init() { func init() {
logPostOnly = conf.GetConfigBool("logPostOnly") logPostOnly = conf.GetConfigBool("logPostOnly")
passwordRegex = regexp.MustCompile("\"password\":\".+\"") passwordRegex = regexp.MustCompile("\"password\":\"([^\"]*?)\"")
} }
type Record struct { type Record struct {

View File

@ -338,6 +338,10 @@ func roleChangeTrigger(oldName string, newName string) error {
for _, role := range roles { for _, role := range roles {
for j, u := range role.Roles { for j, u := range role.Roles {
if u == "*" {
continue
}
owner, name := util.GetOwnerAndNameFromId(u) owner, name := util.GetOwnerAndNameFromId(u)
if name == oldName { if name == oldName {
role.Roles[j] = util.GetId(owner, newName) role.Roles[j] = util.GetId(owner, newName)
@ -358,6 +362,10 @@ func roleChangeTrigger(oldName string, newName string) error {
for _, permission := range permissions { for _, permission := range permissions {
for j, u := range permission.Roles { for j, u := range permission.Roles {
// u = organization/username // u = organization/username
if u == "*" {
continue
}
owner, name := util.GetOwnerAndNameFromId(u) owner, name := util.GetOwnerAndNameFromId(u)
if name == oldName { if name == oldName {
permission.Roles[j] = util.GetId(owner, newName) permission.Roles[j] = util.GetId(owner, newName)

View File

@ -26,6 +26,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strings"
"time" "time"
"github.com/beevik/etree" "github.com/beevik/etree"
@ -222,10 +223,13 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
idpLocation := "" idpLocation := ""
idpBinding := ""
if enablePostBinding { if enablePostBinding {
idpLocation = fmt.Sprintf("%s/api/saml/redirect/%s/%s", originBackend, application.Owner, application.Name) idpLocation = fmt.Sprintf("%s/api/saml/redirect/%s/%s", originBackend, application.Owner, application.Name)
idpBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
} else { } else {
idpLocation = fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name) idpLocation = fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name)
idpBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
} }
d := IdpEntityDescriptor{ d := IdpEntityDescriptor{
@ -258,7 +262,7 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"}, {Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
}, },
SingleSignOnService: SingleSignOnService{ SingleSignOnService: SingleSignOnService{
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", Binding: idpBinding,
Location: idpLocation, Location: idpLocation,
}, },
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
@ -273,29 +277,38 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) { func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
// request type // request type
method := "GET" method := "GET"
samlRequest = strings.ReplaceAll(samlRequest, " ", "+")
// base64 decode // base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest) defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil { if err != nil {
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error()) return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
} }
// decompress var requestByte []byte
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
for { if strings.Contains(string(defated), "xmlns:") {
_, err = io.CopyN(&buffer, rdr, 1024) requestByte = defated
if err != nil { } else {
if err == io.EOF { // decompress
break var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
for {
_, err = io.CopyN(&buffer, rdr, 1024)
if err != nil {
if err == io.EOF {
break
}
return "", "", "", err
} }
return "", "", "", err
} }
requestByte = buffer.Bytes()
} }
var authnRequest saml.AuthNRequest var authnRequest saml.AuthNRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest) err = xml.Unmarshal(requestByte, &authnRequest)
if err != nil { if err != nil {
return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error()) return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error())
} }
@ -325,6 +338,9 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} else if authnRequest.AssertionConsumerServiceURL == "" { } else if authnRequest.AssertionConsumerServiceURL == "" {
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>") return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
} }
if authnRequest.ProtocolBinding == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" {
method = "POST"
}
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)

View File

@ -102,14 +102,6 @@ func GetTokenByAccessToken(accessToken string) (*Token, error) {
return nil, err return nil, err
} }
if !existed {
token = Token{AccessToken: accessToken}
existed, err = ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
}
if !existed { if !existed {
return nil, nil return nil, nil
} }
@ -123,14 +115,6 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
return nil, err return nil, err
} }
if !existed {
token = Token{RefreshToken: refreshToken}
existed, err = ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
}
if !existed { if !existed {
return nil, nil return nil, nil
} }
@ -140,6 +124,7 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) { func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
switch tokenTypeHint { switch tokenTypeHint {
case "access_token": case "access_token":
case "access-token":
token, err := GetTokenByAccessToken(tokenValue) token, err := GetTokenByAccessToken(tokenValue)
if err != nil { if err != nil {
return nil, err return nil, err
@ -148,6 +133,7 @@ func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
return token, nil return token, nil
} }
case "refresh_token": case "refresh_token":
case "refresh-token":
token, err := GetTokenByRefreshToken(tokenValue) token, err := GetTokenByRefreshToken(tokenValue)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -22,6 +22,7 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"math/rand" "math/rand"
"strings"
"sync" "sync"
"time" "time"
@ -184,6 +185,15 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
return proxyTicket return proxyTicket
} }
func escapeXMLText(input string) (string, error) {
var sb strings.Builder
err := xml.EscapeText(&sb, []byte(input))
if err != nil {
return "", err
}
return sb.String(), nil
}
func GenerateCasToken(userId string, service string) (string, error) { func GenerateCasToken(userId string, service string) (string, error) {
user, err := GetUser(userId) user, err := GetUser(userId)
if err != nil { if err != nil {
@ -225,6 +235,11 @@ func GenerateCasToken(userId string, service string) (string, error) {
} }
if value != "" { if value != "" {
if escapedValue, err := escapeXMLText(value); err != nil {
return "", err
} else {
value = escapedValue
}
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{ authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k, Name: k,
Value: value, Value: value,

View File

@ -309,22 +309,29 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
}, nil }, nil
} }
var oldTokenScope string
if application.TokenFormat == "JWT-Standard" { if application.TokenFormat == "JWT-Standard" {
_, err = ParseStandardJwtToken(refreshToken, cert) oldToken, err := ParseStandardJwtToken(refreshToken, cert)
if err != nil { if err != nil {
return &TokenError{ return &TokenError{
Error: InvalidGrant, Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()), ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil }, nil
} }
oldTokenScope = oldToken.Scope
} else { } else {
_, err = ParseJwtToken(refreshToken, cert) oldToken, err := ParseJwtToken(refreshToken, cert)
if err != nil { if err != nil {
return &TokenError{ return &TokenError{
Error: InvalidGrant, Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()), ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil }, nil
} }
oldTokenScope = oldToken.Scope
}
if scope == "" {
scope = oldTokenScope
} }
// generate a new token // generate a new token
@ -504,7 +511,7 @@ func GetPasswordToken(application *Application, username string, password string
} }
if user.Ldap != "" { if user.Ldap != "" {
err = checkLdapUserPassword(user, password, "en") err = CheckLdapUserPassword(user, password, "en")
} else { } else {
err = CheckPassword(user, password, "en") err = CheckPassword(user, password, "en")
} }

View File

@ -129,6 +129,7 @@ type User struct {
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"` Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"` Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin varchar(100)" json:"douyin"` Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Kwai string `xorm:"kwai varchar(100)" json:"kwai"`
Line string `xorm:"line varchar(100)" json:"line"` Line string `xorm:"line varchar(100)" json:"line"`
Amazon string `xorm:"amazon varchar(100)" json:"amazon"` Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"` Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
@ -200,8 +201,9 @@ type User struct {
Permissions []*Permission `json:"permissions"` Permissions []*Permission `json:"permissions"`
Groups []string `xorm:"groups varchar(1000)" json:"groups"` Groups []string `xorm:"groups varchar(1000)" json:"groups"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"` LastChangePasswordTime string `xorm:"varchar(100)" json:"lastChangePasswordTime"`
SigninWrongTimes int `json:"signinWrongTimes"` LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"` ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"` MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
@ -236,6 +238,7 @@ type MfaAccount struct {
AccountName string `xorm:"varchar(100)" json:"accountName"` AccountName string `xorm:"varchar(100)" json:"accountName"`
Issuer string `xorm:"varchar(100)" json:"issuer"` Issuer string `xorm:"varchar(100)" json:"issuer"`
SecretKey string `xorm:"varchar(100)" json:"secretKey"` SecretKey string `xorm:"varchar(100)" json:"secretKey"`
Origin string `xorm:"varchar(100)" json:"origin"`
} }
type FaceId struct { type FaceId struct {
@ -678,6 +681,10 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
user.Password = oldUser.Password user.Password = oldUser.Password
} }
if user.Id != oldUser.Id && user.Id == "" {
user.Id = oldUser.Id
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" { if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false) user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
if err != nil { if err != nil {
@ -690,9 +697,9 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"owner", "display_name", "avatar", "first_name", "last_name", "owner", "display_name", "avatar", "first_name", "last_name",
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application", "location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts", "is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", "signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs", "github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon", "baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon",
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox", "auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup", "eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud", "microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
@ -816,6 +823,10 @@ func AddUser(user *User) (bool, error) {
user.UpdateUserPassword(organization) user.UpdateUserPassword(organization)
} }
if user.CreatedTime == "" {
user.CreatedTime = util.GetCurrentTime()
}
err = user.UpdateUserHash() err = user.UpdateUserHash()
if err != nil { if err != nil {
return false, err return false, err
@ -835,11 +846,14 @@ func AddUser(user *User) (bool, error) {
} }
} }
count, err := GetUserCount(user.Owner, "", "", "") rankingItem := GetAccountItemByName("Ranking", organization)
if err != nil { if rankingItem != nil {
return false, err count, err := GetUserCount(user.Owner, "", "", "")
if err != nil {
return false, err
}
user.Ranking = int(count + 1)
} }
user.Ranking = int(count + 1)
if user.Groups != nil && len(user.Groups) > 0 { if user.Groups != nil && len(user.Groups) > 0 {
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups) _, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)

View File

@ -57,7 +57,7 @@ type VerificationRecord struct {
Receiver string `xorm:"varchar(100) index notnull" json:"receiver"` Receiver string `xorm:"varchar(100) index notnull" json:"receiver"`
Code string `xorm:"varchar(10) notnull" json:"code"` Code string `xorm:"varchar(10) notnull" json:"code"`
Time int64 `xorm:"notnull" json:"time"` Time int64 `xorm:"notnull" json:"time"`
IsUsed bool IsUsed bool `xorm:"notnull" json:"isUsed"`
} }
func IsAllowSend(user *User, remoteAddr, recordType string) error { func IsAllowSend(user *User, remoteAddr, recordType string) error {

View File

@ -68,8 +68,10 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password) log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" { if organization == "" {
w.Write(r.Response(radius.CodeAccessReject)) organization = conf.GetConfigString("radiusDefaultOrganization")
return if organization == "" {
organization = "built-in"
}
} }
var user *object.User var user *object.User

View File

@ -16,11 +16,11 @@ package routers
import ( import (
"net/http" "net/http"
"strings"
"github.com/beego/beego/context" "github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
) )
const ( const (
@ -52,7 +52,13 @@ func CorsFilter(ctx *context.Context) {
origin = "" origin = ""
} }
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") { isValid, err := util.IsValidOrigin(origin)
if err != nil {
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
responseError(ctx, err.Error())
return
}
if isValid {
setCorsHeaders(ctx, origin) setCorsHeaders(ctx, origin)
return return
} }

View File

@ -174,6 +174,8 @@ func initAPI() {
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions") beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles") beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
beego.Router("/api/run-casbin-command", &controllers.ApiController{}, "GET:RunCasbinCommand")
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions") beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession") beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
beego.Router("/api/update-session", &controllers.ApiController{}, "POST:UpdateSession") beego.Router("/api/update-session", &controllers.ApiController{}, "POST:UpdateSession")

View File

@ -133,6 +133,11 @@ func StaticFilter(ctx *context.Context) {
path += urlPath path += urlPath
} }
err := appendThemeCookie(ctx, urlPath)
if err != nil {
fmt.Println(err)
}
if strings.Contains(path, "/../") || !util.FileExist(path) { if strings.Contains(path, "/../") || !util.FileExist(path) {
path = webBuildFolder + "/index.html" path = webBuildFolder + "/index.html"
} }

76
routers/theme_filter.go Normal file
View File

@ -0,0 +1,76 @@
// 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 routers
import (
"encoding/json"
"fmt"
"strings"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/object"
)
func appendThemeCookie(ctx *context.Context, urlPath string) error {
if urlPath == "/login" {
organization, err := object.GetOrganization(fmt.Sprintf("admin/built-in"))
if err != nil {
return err
}
if organization != nil {
return setThemeDataCookie(ctx, organization.ThemeData)
}
} else if strings.HasPrefix(urlPath, "/login/oauth/authorize") {
clientId := ctx.Input.Query("client_id")
if clientId == "" {
return nil
}
application, err := object.GetApplicationByClientId(clientId)
if err != nil {
return err
}
if application != nil {
organization, err := object.GetOrganization(fmt.Sprintf("admin/%s", application.Organization))
if err != nil {
return err
}
if organization != nil {
return setThemeDataCookie(ctx, organization.ThemeData)
}
}
} else if strings.HasPrefix(urlPath, "/login/") {
owner := strings.Replace(urlPath, "/login/", "", -1)
if owner != "undefined" && owner != "oauth/undefined" {
organization, err := object.GetOrganization(fmt.Sprintf("admin/%s", owner))
if err != nil {
return err
}
if organization != nil {
return setThemeDataCookie(ctx, organization.ThemeData)
}
}
}
return nil
}
func setThemeDataCookie(ctx *context.Context, themeData *object.ThemeData) error {
themeDataString, err := json.Marshal(themeData)
if err != nil {
return err
}
ctx.SetCookie("organizationTheme", string(themeDataString))
return nil
}

21
storage/cucloud_oss.go Normal file
View File

@ -0,0 +1,21 @@
package storage
import (
awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/casdoor/oss"
"github.com/casdoor/oss/s3"
)
func NewCUCloudOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := s3.New(&s3.Config{
AccessID: clientId,
AccessKey: clientSecret,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
S3Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
})
return sp
}

View File

@ -23,7 +23,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
case "AWS S3": case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "MinIO": case "MinIO":
return NewMinIOS3StorageProvider(clientId, clientSecret, "_", bucket, endpoint), nil if region == "" {
region = "_"
}
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "Aliyun OSS": case "Aliyun OSS":
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "Tencent Cloud COS": case "Tencent Cloud COS":
@ -38,6 +41,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
case "Casdoor": case "Casdoor":
return NewCasdoorStorageProvider(providerType, clientId, clientSecret, region, bucket, endpoint, cert, content), nil return NewCasdoorStorageProvider(providerType, clientId, clientSecret, region, bucket, endpoint, cert, content), nil
case "CUCloud OSS":
return NewCUCloudOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
} }
return nil, nil return nil, nil

View File

@ -7558,6 +7558,9 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"kwai": {
"type": "string"
},
"language": { "language": {
"type": "string" "type": "string"
}, },

View File

@ -4981,6 +4981,8 @@ definitions:
karma: karma:
type: integer type: integer
format: int64 format: int64
kwai:
type: string
language: language:
type: string type: string
lark: lark:

97
util/process.go Normal file
View File

@ -0,0 +1,97 @@
// 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 util
import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
func getPidByPort(port int) (int, error) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "netstat -ano | findstr :"+strconv.Itoa(port))
case "darwin", "linux":
cmd = exec.Command("lsof", "-t", "-i", ":"+strconv.Itoa(port))
default:
return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
output, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return 0, nil
}
} else {
return 0, err
}
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) > 0 {
if runtime.GOOS == "windows" {
if fields[1] == "0.0.0.0:"+strconv.Itoa(port) {
pid, err := strconv.Atoi(fields[len(fields)-1])
if err != nil {
return 0, err
}
return pid, nil
}
} else {
pid, err := strconv.Atoi(fields[0])
if err != nil {
return 0, err
}
return pid, nil
}
}
}
return 0, nil
}
func StopOldInstance(port int) error {
pid, err := getPidByPort(port)
if err != nil {
return err
}
if pid == 0 {
return nil
}
process, err := os.FindProcess(pid)
if err != nil {
return err
}
err = process.Kill()
if err != nil {
return err
} else {
fmt.Printf("The old instance with pid: %d has been stopped\n", pid)
}
return nil
}

View File

@ -17,6 +17,7 @@ package util
import ( import (
"fmt" "fmt"
"net/mail" "net/mail"
"net/url"
"regexp" "regexp"
"strings" "strings"
@ -24,10 +25,11 @@ import (
) )
var ( var (
rePhone *regexp.Regexp rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp ReUserName *regexp.Regexp
ReUserNameWithEmail *regexp.Regexp
) )
func init() { func init() {
@ -35,6 +37,7 @@ func init() {
ReWhiteSpace, _ = regexp.Compile(`\s`) ReWhiteSpace, _ = regexp.Compile(`\s`)
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`) ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*$") ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*$")
ReUserNameWithEmail, _ = regexp.Compile(`^([a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*)|([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$`) // Add support for email formats
} }
func IsEmailValid(email string) bool { func IsEmailValid(email string) bool {
@ -100,3 +103,21 @@ func GetCountryCode(prefix string, phone string) (string, error) {
func FilterField(field string) bool { func FilterField(field string) bool {
return ReFieldWhiteList.MatchString(field) return ReFieldWhiteList.MatchString(field)
} }
func IsValidOrigin(origin string) (bool, error) {
urlObj, err := url.Parse(origin)
if err != nil {
return false, err
}
if urlObj == nil {
return false, nil
}
originHostOnly := ""
if urlObj.Host != "" {
originHostOnly = fmt.Sprintf("%s://%s", urlObj.Scheme, urlObj.Hostname())
}
res := originHostOnly == "http://localhost" || originHostOnly == "https://localhost" || originHostOnly == "http://127.0.0.1" || originHostOnly == "http://casdoor-app" || strings.HasSuffix(originHostOnly, ".chromiumapp.org")
return res, nil
}

View File

@ -1,4 +1,5 @@
const CracoLessPlugin = require("craco-less"); const CracoLessPlugin = require("craco-less");
const path = require("path");
module.exports = { module.exports = {
devServer: { devServer: {
@ -55,47 +56,42 @@ module.exports = {
}, },
], ],
webpack: { webpack: {
configure: { configure: (webpackConfig, { env, paths }) => {
// ignore webpack warnings by source-map-loader paths.appBuild = path.resolve(__dirname, "build-temp");
webpackConfig.output.path = path.resolve(__dirname, "build-temp");
// ignore webpack warnings by source-map-loader
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546 // https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
ignoreWarnings: [ webpackConfig.ignoreWarnings = [
function ignoreSourcemapsloaderWarnings(warning) { function ignoreSourcemapsloaderWarnings(warning) {
return ( return (
warning.module && warning.module &&
warning.module.resource.includes('node_modules') && warning.module.resource.includes("node_modules") &&
warning.details && warning.details &&
warning.details.includes('source-map-loader') warning.details.includes("source-map-loader")
) );
}, },
], ];
// use polyfill Buffer with Webpack 5 // use polyfill Buffer with Webpack 5
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5 // https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
// https://craco.js.org/docs/configuration/webpack/ // https://craco.js.org/docs/configuration/webpack/
resolve: { webpackConfig.resolve.fallback = {
fallback: { buffer: require.resolve("buffer/"),
// "process": require.resolve('process/browser'), process: false,
// "util": require.resolve("util/"), util: false,
// "url": require.resolve("url/"), url: false,
// "zlib": require.resolve("browserify-zlib"), zlib: false,
// "stream": require.resolve("stream-browserify"), stream: false,
// "http": require.resolve("stream-http"), http: false,
// "https": require.resolve("https-browserify"), https: false,
// "assert": require.resolve("assert/"), assert: false,
"buffer": require.resolve('buffer/'), crypto: false,
"process": false, os: false,
"util": false, fs: false,
"url": false, };
"zlib": false,
"stream": false, return webpackConfig;
"http": false,
"https": false,
"assert": false,
"buffer": false,
"crypto": false,
"os": false,
"fs": false,
},
}
}, },
} },
}; };

21
web/mv.js Normal file
View File

@ -0,0 +1,21 @@
const fs = require("fs");
const path = require("path");
const sourceDir = path.join(__dirname, "build-temp");
const targetDir = path.join(__dirname, "build");
if (!fs.existsSync(sourceDir)) {
// eslint-disable-next-line no-console
console.error(`Source directory "${sourceDir}" does not exist.`);
process.exit(1);
}
if (fs.existsSync(targetDir)) {
fs.rmSync(targetDir, {recursive: true, force: true});
// eslint-disable-next-line no-console
console.log(`Target directory "${targetDir}" has been deleted successfully.`);
}
fs.renameSync(sourceDir, targetDir);
// eslint-disable-next-line no-console
console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`);

View File

@ -57,6 +57,7 @@
"scripts": { "scripts": {
"start": "cross-env PORT=7001 craco start", "start": "cross-env PORT=7001 craco start",
"build": "craco build", "build": "craco build",
"postbuild": "node mv.js",
"test": "craco test", "test": "craco test",
"eject": "craco eject", "eject": "craco eject",
"crowdin:sync": "crowdin upload && crowdin download", "crowdin:sync": "crowdin upload && crowdin download",

View File

@ -36,6 +36,7 @@ const {Footer, Content} = Layout;
import {setTwoToneColor} from "@ant-design/icons"; import {setTwoToneColor} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Cookie from "cookie";
setTwoToneColor("rgb(87,52,211)"); setTwoToneColor("rgb(87,52,211)");
@ -308,7 +309,7 @@ class App extends Component {
AI Assistant AI Assistant
</a> </a>
</Tooltip> </Tooltip>
<a className="custom-link" style={{float: "right", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://ai.casbin.com"}> <a className="custom-link" style={{float: "right", marginTop: "2px"}} target="_blank" rel="noreferrer" href={`${Conf.AiAssistantUrl}`}>
<ShareAltOutlined className="custom-link" style={{fontSize: "20px", color: "rgb(140,140,140)"}} /> <ShareAltOutlined className="custom-link" style={{fontSize: "20px", color: "rgb(140,140,140)"}} />
</a> </a>
<a className="custom-link" style={{float: "right", marginRight: "30px", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://github.com/casibase/casibase"}> <a className="custom-link" style={{float: "right", marginRight: "30px", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://github.com/casibase/casibase"}>
@ -326,7 +327,7 @@ class App extends Component {
}} }}
visible={this.state.isAiAssistantOpen} visible={this.state.isAiAssistantOpen}
> >
<iframe id="iframeHelper" title={"iframeHelper"} src={"https://ai.casbin.com/?isRaw=1"} width="100%" height="100%" scrolling="no" frameBorder="no" /> <iframe id="iframeHelper" title={"iframeHelper"} src={`${Conf.AiAssistantUrl}/?isRaw=1`} width="100%" height="100%" scrolling="no" frameBorder="no" />
</Drawer> </Drawer>
); );
} }
@ -360,11 +361,19 @@ class App extends Component {
renderPage() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
let themeData = this.state.themeData;
if (this.state.organization === undefined) {
const curCookie = Cookie.parse(document.cookie);
if (curCookie["organizationTheme"] && curCookie["organizationTheme"] !== "null") {
themeData = JSON.parse(curCookie["organizationTheme"]);
}
}
return ( return (
<ConfigProvider theme={{ <ConfigProvider theme={{
token: { token: {
colorPrimary: this.state.themeData.colorPrimary, colorPrimary: themeData.colorPrimary,
borderRadius: this.state.themeData.borderRadius, borderRadius: themeData.borderRadius,
}, },
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm), algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
}}> }}>

View File

@ -603,7 +603,7 @@ class ApplicationEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} : {Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input placeholder = {this.state.application.organizationObj?.ipWhitelist} value={this.state.application.ipWhiteList} onChange={e => { <Input placeholder = {this.state.application.organizationObj?.ipWhitelist} value={this.state.application.ipWhitelist} onChange={e => {
this.updateApplicationField("ipWhitelist", e.target.value); this.updateApplicationField("ipWhitelist", e.target.value);
}} /> }} />
</Col> </Col>
@ -765,7 +765,7 @@ class ApplicationEditPage extends React.Component {
/> />
<br /> <br />
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => { <Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&post=${this.state.application.enableSamlPostBinding}`); copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&enablePostBinding=${this.state.application.enableSamlPostBinding}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully")); Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}} }}
> >

View File

@ -19,6 +19,7 @@ import "codemirror/mode/properties/properties";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import IframeEditor from "./IframeEditor"; import IframeEditor from "./IframeEditor";
import {Tabs} from "antd"; import {Tabs} from "antd";
import i18next from "i18next";
const {TabPane} = Tabs; const {TabPane} = Tabs;
@ -68,8 +69,8 @@ const CasbinEditor = ({model, onModelTextChange}) => {
return ( return (
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}> <div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}> <Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
<TabPane tab="Basic Editor" key="basic" /> <TabPane tab={i18next.t("model:Basic Editor")} key="basic" />
<TabPane tab="Advanced Editor" key="advanced" /> <TabPane tab={i18next.t("model:Advanced Editor")} key="advanced" />
</Tabs> </Tabs>
<div style={{flex: "1 1 auto", overflow: "hidden"}}> <div style={{flex: "1 1 auto", overflow: "hidden"}}>
{activeKey === "advanced" ? ( {activeKey === "advanced" ? (

View File

@ -31,3 +31,6 @@ export const ThemeDefault = {
}; };
export const CustomFooter = null; export const CustomFooter = null;
// Blank or null to hide Ai Assistant button
export const AiAssistantUrl = "https://ai.casbin.com";

View File

@ -17,6 +17,7 @@ import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} fro
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => { const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
const iframeRef = useRef(null); const iframeRef = useRef(null);
const [iframeReady, setIframeReady] = useState(false); const [iframeReady, setIframeReady] = useState(false);
const currentLang = localStorage.getItem("language") || "en";
useEffect(() => { useEffect(() => {
const handleMessage = (event) => { const handleMessage = (event) => {
@ -26,24 +27,31 @@ const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) =>
onModelTextChange(event.data.modelText); onModelTextChange(event.data.modelText);
} else if (event.data.type === "iframeReady") { } else if (event.data.type === "iframeReady") {
setIframeReady(true); setIframeReady(true);
iframeRef.current?.contentWindow.postMessage({ if (initialModelText && iframeRef.current?.contentWindow) {
type: "initializeModel", iframeRef.current.contentWindow.postMessage({
modelText: initialModelText, type: "initializeModel",
}, "*"); modelText: initialModelText,
lang: currentLang,
}, "*");
}
} }
}; };
window.addEventListener("message", handleMessage); window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage);
}, [onModelTextChange, initialModelText]); }, [onModelTextChange, initialModelText, currentLang]);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
getModelText: () => { getModelText: () => {
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*"); if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "getModelText",
}, "*");
}
}, },
updateModelText: (newModelText) => { updateModelText: (newModelText) => {
if (iframeReady) { if (iframeReady && iframeRef.current?.contentWindow) {
iframeRef.current?.contentWindow.postMessage({ iframeRef.current.contentWindow.postMessage({
type: "updateModelText", type: "updateModelText",
modelText: newModelText, modelText: newModelText,
}, "*"); }, "*");
@ -54,7 +62,7 @@ const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) =>
return ( return (
<iframe <iframe
ref={iframeRef} ref={iframeRef}
src="https://editor.casbin.org/model-editor" src={`https://editor.casbin.org/model-editor?lang=${currentLang}`}
frameBorder="0" frameBorder="0"
width="100%" width="100%"
height="500px" height="500px"

View File

@ -106,6 +106,22 @@ class InvitationEditPage extends React.Component {
}); });
} }
copySignupLink() {
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
const selectedOrganization = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner);
defaultApplication = selectedOrganization.defaultApplication;
if (!defaultApplication) {
Setting.showMessage("error", i18next.t("invitation:You need to specify a default application for ") + selectedOrganization.name);
return;
}
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}
renderInvitation() { renderInvitation() {
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan"; const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
return ( return (
@ -114,16 +130,7 @@ class InvitationEditPage extends React.Component {
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
<Button style={{marginLeft: "20px"}} onClick={() => { <Button style={{marginLeft: "20px"}} onClick={_ => this.copySignupLink()}>
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")} {i18next.t("application:Copy signup page URL")}
</Button> </Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
@ -330,16 +337,7 @@ class InvitationEditPage extends React.Component {
<div style={{marginTop: "20px", marginLeft: "40px"}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
<Button style={{marginLeft: "20px"}} size="large" onClick={() => { <Button style={{marginLeft: "20px"}} size="large" onClick={_ => this.copySignupLink()}>
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")} {i18next.t("application:Copy signup page URL")}
</Button> </Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}

View File

@ -192,17 +192,21 @@ function ManagementPage(props) {
themeAlgorithm={props.themeAlgorithm} themeAlgorithm={props.themeAlgorithm}
onChange={props.setLogoAndThemeAlgorithm} /> onChange={props.setLogoAndThemeAlgorithm} />
<LanguageSelect languages={props.account.organization.languages} /> <LanguageSelect languages={props.account.organization.languages} />
<Tooltip title="Click to open AI assitant"> {
<div className="select-box" onClick={props.openAiAssistant}> Conf.AiAssistantUrl?.trim() && (
<DeploymentUnitOutlined style={{fontSize: "24px"}} /> <Tooltip title="Click to open AI assistant">
</div> <div className="select-box" onClick={props.openAiAssistant}>
</Tooltip> <DeploymentUnitOutlined style={{fontSize: "24px"}} />
</div>
</Tooltip>
)
}
<OpenTour /> <OpenTour />
{Setting.isAdminUser(props.account) && !Setting.isMobile() && (props.uri.indexOf("/trees") === -1) && {Setting.isAdminUser(props.account) && (props.uri.indexOf("/trees") === -1) &&
<OrganizationSelect <OrganizationSelect
initValue={Setting.getOrganization()} initValue={Setting.getOrganization()}
withAll={true} withAll={true}
style={{marginRight: "20px", width: "180px", display: "flex"}} style={{marginRight: "20px", width: "180px", display: !Setting.isMobile() ? "flex" : "none"}}
onChange={(value) => { onChange={(value) => {
Setting.setOrganization(value); Setting.setOrganization(value);
}} }}

View File

@ -339,6 +339,16 @@ class OrganizationEditPage extends React.Component {
</Col> </Col>
</Row>) </Row>)
} }
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Password expire days"), i18next.t("organization:Password expire days - Tooltip"))} :
</Col>
<Col span={4} >
<InputNumber value={this.state.organization.passwordExpireDays} onChange={value => {
this.updateOrganizationField("passwordExpireDays", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} : {Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :

View File

@ -37,6 +37,7 @@ class OrganizationListPage extends BaseListPage {
passwordOptions: [], passwordOptions: [],
passwordObfuscatorType: "Plain", passwordObfuscatorType: "Plain",
passwordObfuscatorKey: "", passwordObfuscatorKey: "",
passwordExpireDays: 0,
countryCodes: ["US"], countryCodes: ["US"],
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`, defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "", defaultApplication: "",

View File

@ -123,6 +123,22 @@ class ProductBuyPage extends React.Component {
return "$"; return "$";
} else if (product?.currency === "CNY") { } else if (product?.currency === "CNY") {
return "¥"; return "¥";
} else if (product?.currency === "EUR") {
return "€";
} else if (product?.currency === "JPY") {
return "¥";
} else if (product?.currency === "GBP") {
return "£";
} else if (product?.currency === "AUD") {
return "A$";
} else if (product?.currency === "CAD") {
return "C$";
} else if (product?.currency === "CHF") {
return "CHF";
} else if (product?.currency === "HKD") {
return "HK$";
} else if (product?.currency === "SGD") {
return "S$";
} else { } else {
return "(Unknown currency)"; return "(Unknown currency)";
} }

View File

@ -209,6 +209,14 @@ class ProductEditPage extends React.Component {
[ [
{id: "USD", name: "USD"}, {id: "USD", name: "USD"},
{id: "CNY", name: "CNY"}, {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"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>

View File

@ -297,6 +297,8 @@ class ProviderEditPage extends React.Component {
return Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip")); return Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"));
} else if (provider.type === "WeChat Pay") { } else if (provider.type === "WeChat Pay") {
return Setting.getLabel(i18next.t("provider:App ID"), i18next.t("provider:App ID - Tooltip")); return Setting.getLabel(i18next.t("provider:App ID"), i18next.t("provider:App ID - Tooltip"));
} else if (provider.type === "CUCloud") {
return Setting.getLabel(i18next.t("provider:Account ID"), i18next.t("provider:Account ID - Tooltip"));
} else { } else {
return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip")); return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"));
} }
@ -393,6 +395,9 @@ class ProviderEditPage extends React.Component {
} else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") { } else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
text = i18next.t("provider:App Key"); text = i18next.t("provider:App Key");
tooltip = i18next.t("provider:App Key - Tooltip"); tooltip = i18next.t("provider:App Key - Tooltip");
} else if (provider.type === "CUCloud") {
text = i18next.t("provider:Topic name");
tooltip = i18next.t("provider:Topic name - Tooltip");
} }
} }
@ -633,6 +638,20 @@ class ProviderEditPage extends React.Component {
</React.Fragment> </React.Fragment>
) )
} }
{
this.state.provider.category === "OAuth" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={this.state.provider.emailRegex} onChange={e => {
this.updateProviderField("emailRegex", e.target.value);
}} />
</Col>
</Row>
) : null
}
{ {
this.state.provider.type === "Custom" ? ( this.state.provider.type === "Custom" ? (
<React.Fragment> <React.Fragment>
@ -757,7 +776,7 @@ class ProviderEditPage extends React.Component {
) )
} }
{ {
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : ( this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "CUCloud" ? null : (
<React.Fragment> <React.Fragment>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
@ -770,7 +789,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
{ {
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : ( (this.state.provider.type === "WeChat Pay" || this.state.provider.type === "CUCloud") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientSecret2Label(this.state.provider)} : {this.getClientSecret2Label(this.state.provider)} :
@ -856,9 +875,9 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
) )
} }
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? ( {this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email", "CUCloud"].includes(this.state.provider.type) ? (
<div> <div>
{["Local File System"].includes(this.state.provider.type) ? null : ( {["Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} : {Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
@ -870,7 +889,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} : {Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
@ -882,7 +901,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ? {["Casdoor"].includes(this.state.provider.type) ?
@ -896,7 +915,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
@ -908,7 +927,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@ -932,7 +951,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
) : null} ) : null}
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor"].includes(this.state.provider.type) ? ( {["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO", "CUCloud"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ? {["Casdoor"].includes(this.state.provider.type) ?
@ -971,7 +990,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
) : null} ) : null}
{["Custom HTTP"].includes(this.state.provider.type) ? ( {["Custom HTTP", "CUCloud"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
@ -983,7 +1002,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
) : null} ) : null}
{["Google Chat"].includes(this.state.provider.type) ? ( {["Google Chat", "CUCloud"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :

View File

@ -187,7 +187,7 @@ class RoleEditPage extends React.Component {
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.role.users} <Select virtual={true} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
onChange={(value => {this.updateRoleField("users", value);})} onChange={(value => {this.updateRoleField("users", value);})}
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))} options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
/> />

View File

@ -233,6 +233,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/casdoor.png`, logo: `${StaticBaseUrl}/img/casdoor.png`,
url: "https://casdoor.org/docs/provider/storage/overview", url: "https://casdoor.org/docs/provider/storage/overview",
}, },
"CUCloud OSS": {
logo: `${StaticBaseUrl}/img/social_cucloud.png`,
url: "https://www.cucloud.cn/product/oss.html",
},
}, },
SAML: { SAML: {
"Aliyun IDaaS": { "Aliyun IDaaS": {
@ -401,6 +405,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_viber.png`, logo: `${StaticBaseUrl}/img/social_viber.png`,
url: "https://www.viber.com/", url: "https://www.viber.com/",
}, },
"CUCloud": {
logo: `${StaticBaseUrl}/img/cucloud.png`,
url: "https://www.cucloud.cn/",
},
}, },
}; };
@ -920,7 +928,7 @@ export function getClickable(text) {
return ( return (
<a onClick={() => { <a onClick={() => {
copy(text); copy(text);
showMessage("success", "Copied to clipboard"); showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}> }}>
{text} {text}
</a> </a>
@ -981,6 +989,7 @@ export function getProviderTypeOptions(category) {
{id: "Bilibili", name: "Bilibili"}, {id: "Bilibili", name: "Bilibili"},
{id: "Okta", name: "Okta"}, {id: "Okta", name: "Okta"},
{id: "Douyin", name: "Douyin"}, {id: "Douyin", name: "Douyin"},
{id: "Kwai", name: "Kwai"},
{id: "Line", name: "Line"}, {id: "Line", name: "Line"},
{id: "Amazon", name: "Amazon"}, {id: "Amazon", name: "Amazon"},
{id: "Auth0", name: "Auth0"}, {id: "Auth0", name: "Auth0"},
@ -1078,6 +1087,7 @@ export function getProviderTypeOptions(category) {
{id: "Google Cloud Storage", name: "Google Cloud Storage"}, {id: "Google Cloud Storage", name: "Google Cloud Storage"},
{id: "Synology", name: "Synology"}, {id: "Synology", name: "Synology"},
{id: "Casdoor", name: "Casdoor"}, {id: "Casdoor", name: "Casdoor"},
{id: "CUCloud OSS", name: "CUCloud OSS"},
] ]
); );
} else if (category === "SAML") { } else if (category === "SAML") {
@ -1131,6 +1141,7 @@ export function getProviderTypeOptions(category) {
{id: "Reddit", name: "Reddit"}, {id: "Reddit", name: "Reddit"},
{id: "Rocket Chat", name: "Rocket Chat"}, {id: "Rocket Chat", name: "Rocket Chat"},
{id: "Viber", name: "Viber"}, {id: "Viber", name: "Viber"},
{id: "CUCloud", name: "CUCloud"},
]); ]);
} else { } else {
return []; return [];
@ -1171,7 +1182,7 @@ export function renderLogo(application) {
function isSigninMethodEnabled(application, signinMethod) { function isSigninMethodEnabled(application, signinMethod) {
if (application && application.signinMethods) { if (application && application.signinMethods) {
return application.signinMethods.filter(item => item.name === signinMethod && item.rule !== "Hide-Password").length > 0; return application.signinMethods.filter(item => item.name === signinMethod && item.rule !== "Hide password").length > 0;
} else { } else {
return false; return false;
} }
@ -1550,9 +1561,25 @@ export function getDefaultHtmlEmailContent() {
export function getCurrencyText(product) { export function getCurrencyText(product) {
if (product?.currency === "USD") { if (product?.currency === "USD") {
return i18next.t("product:USD"); return i18next.t("currency:USD");
} else if (product?.currency === "CNY") { } else if (product?.currency === "CNY") {
return i18next.t("product:CNY"); return i18next.t("currency:CNY");
} else if (product?.currency === "EUR") {
return i18next.t("currency:EUR");
} else if (product?.currency === "JPY") {
return i18next.t("currency:JPY");
} else if (product?.currency === "GBP") {
return i18next.t("currency:GBP");
} else if (product?.currency === "AUD") {
return i18next.t("currency:AUD");
} else if (product?.currency === "CAD") {
return i18next.t("currency:CAD");
} else if (product?.currency === "CHF") {
return i18next.t("currency:CHF");
} else if (product?.currency === "HKD") {
return i18next.t("currency:HKD");
} else if (product?.currency === "SGD") {
return i18next.t("currency:SGD");
} else { } else {
return "(Unknown currency)"; return "(Unknown currency)";
} }

View File

@ -1009,6 +1009,19 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
); );
} else if (accountItem.name === "Last change password time") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Last change password time"), i18next.t("user:Last change password time"))} :
</Col>
<Col span={22}>
<Input value={this.state.user.lastChangePasswordTime} onChange={e => {
this.updateUserField("lastChangePasswordTime", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Managed accounts") { } else if (accountItem.name === "Managed accounts") {
return ( return (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >

View File

@ -204,7 +204,7 @@ class AuthCallback extends React.Component {
} }
const SAMLResponse = res.data; const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl; const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
} }
} }
} else { } else {

View File

@ -0,0 +1,31 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({width = 24, height = 24}) {
return <img src={`${StaticBaseUrl}/buttons/kwai.svg`} alt="Sign in with Kwai" style={{width: width, height: height}} />;
}
const config = {
text: "Sign in with Kwai",
icon: Icon,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const KwaiLoginButton = createButton(config);
export default KwaiLoginButton;

View File

@ -227,7 +227,26 @@ class LoginPage extends React.Component {
return "password"; return "password";
} }
getPlaceholder() { getCurrentLoginMethod() {
if (this.state.loginMethod === "password") {
return "Password";
} else if (this.state.loginMethod?.includes("verificationCode")) {
return "Verification code";
} else if (this.state.loginMethod === "webAuthn") {
return "WebAuthn";
} else if (this.state.loginMethod === "ldap") {
return "LDAP";
} else if (this.state.loginMethod === "faceId") {
return "Face ID";
} else {
return "Password";
}
}
getPlaceholder(defaultPlaceholder = null) {
if (defaultPlaceholder) {
return defaultPlaceholder;
}
switch (this.state.loginMethod) { switch (this.state.loginMethod) {
case "verificationCode": return i18next.t("login:Email or phone"); case "verificationCode": return i18next.t("login:Email or phone");
case "verificationCodeEmail": return i18next.t("login:Email"); case "verificationCodeEmail": return i18next.t("login:Email");
@ -262,17 +281,7 @@ class LoginPage extends React.Component {
values["organization"] = this.getApplicationObj().organization; values["organization"] = this.getApplicationObj().organization;
} }
if (this.state.loginMethod === "password") { values["signinMethod"] = this.getCurrentLoginMethod();
values["signinMethod"] = "Password";
} else if (this.state.loginMethod?.includes("verificationCode")) {
values["signinMethod"] = "Verification code";
} else if (this.state.loginMethod === "webAuthn") {
values["signinMethod"] = "WebAuthn";
} else if (this.state.loginMethod === "ldap") {
values["signinMethod"] = "LDAP";
} else if (this.state.loginMethod === "faceId") {
values["signinMethod"] = "Face ID";
}
const oAuthParams = Util.getOAuthGetParameters(); const oAuthParams = Util.getOAuthGetParameters();
values["type"] = oAuthParams?.responseType ?? this.state.type; values["type"] = oAuthParams?.responseType ?? this.state.type;
@ -409,6 +418,7 @@ class LoginPage extends React.Component {
if (this.state.type === "cas") { if (this.state.type === "cas") {
// CAS // CAS
const casParams = Util.getCasParameters(); const casParams = Util.getCasParameters();
values["signinMethod"] = this.getCurrentLoginMethod();
values["type"] = this.state.type; values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => { AuthBackend.loginCas(values, casParams).then((res) => {
const loginHandler = (res) => { const loginHandler = (res) => {
@ -437,8 +447,8 @@ class LoginPage extends React.Component {
formValues={values} formValues={values}
authParams={casParams} authParams={casParams}
application={this.getApplicationObj()} application={this.getApplicationObj()}
onFail={() => { onFail={(errorMessage) => {
Setting.showMessage("error", i18next.t("mfa:Verification failed")); Setting.showMessage("error", errorMessage);
}} }}
onSuccess={(res) => loginHandler(res)} onSuccess={(res) => loginHandler(res)}
/>); />);
@ -478,6 +488,10 @@ class LoginPage extends React.Component {
const accessToken = res.data; const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${amendatoryResponseType}=${accessToken}&state=${oAuthParams.state}&token_type=bearer`); Setting.goToLink(`${oAuthParams.redirectUri}#${amendatoryResponseType}=${accessToken}&state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "saml") { } else if (responseType === "saml") {
if (res.data === RequiredMfa) {
this.props.onLoginSuccess(window.location.href);
return;
}
if (res.data2.needUpdatePassword) { if (res.data2.needUpdatePassword) {
sessionStorage.setItem("signinUrl", window.location.href); sessionStorage.setItem("signinUrl", window.location.href);
Setting.goToLink(this, `/forget/${this.state.applicationName}`); Setting.goToLink(this, `/forget/${this.state.applicationName}`);
@ -491,7 +505,7 @@ class LoginPage extends React.Component {
} else { } else {
const SAMLResponse = res.data; const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl; const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
} }
} }
}; };
@ -506,8 +520,8 @@ class LoginPage extends React.Component {
formValues={values} formValues={values}
authParams={oAuthParams} authParams={oAuthParams}
application={this.getApplicationObj()} application={this.getApplicationObj()}
onFail={() => { onFail={(errorMessage) => {
Setting.showMessage("error", i18next.t("mfa:Verification failed")); Setting.showMessage("error", errorMessage);
}} }}
onSuccess={(res) => loginHandler(res)} onSuccess={(res) => loginHandler(res)}
/>); />);
@ -672,7 +686,7 @@ class LoginPage extends React.Component {
id="input" id="input"
className="login-username-input" className="login-username-input"
prefix={<UserOutlined className="site-form-item-icon" />} prefix={<UserOutlined className="site-form-item-icon" />}
placeholder={this.getPlaceholder()} placeholder={this.getPlaceholder(signinItem.placeholder)}
onChange={e => { onChange={e => {
this.setState({ this.setState({
username: e.target.value, username: e.target.value,
@ -1086,7 +1100,7 @@ class LoginPage extends React.Component {
className="login-password-input" className="login-password-input"
prefix={<LockOutlined className="site-form-item-icon" />} prefix={<LockOutlined className="site-form-item-icon" />}
type="password" type="password"
placeholder={i18next.t("general:Password")} placeholder={signinItem.placeholder ? signinItem.placeholder : i18next.t("general:Password")}
disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)} disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
/> />
</Form.Item> </Form.Item>
@ -1136,7 +1150,7 @@ class LoginPage extends React.Component {
]); ]);
application?.signinMethods?.forEach((signinMethod) => { application?.signinMethods?.forEach((signinMethod) => {
if (signinMethod.rule === "Hide-Password") { if (signinMethod.rule === "Hide password") {
return; return;
} }
const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule)); const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));

View File

@ -37,7 +37,7 @@ class MfaSetupPage extends React.Component {
this.state = { this.state = {
account: props.account, account: props.account,
application: null, application: null,
applicationName: props.account.signupApplication ?? "", applicationName: props.account.signupApplication ?? localStorage.getItem("applicationName") ?? "",
current: location.state?.from !== undefined ? 1 : 0, current: location.state?.from !== undefined ? 1 : 0,
mfaProps: null, mfaProps: null,
mfaType: params.get("mfaType") ?? SmsMfaType, mfaType: params.get("mfaType") ?? SmsMfaType,
@ -179,8 +179,10 @@ class MfaSetupPage extends React.Component {
mfaProps={this.state.mfaProps} mfaProps={this.state.mfaProps}
application={this.state.application} application={this.state.application}
user={this.props.account} user={this.props.account}
onSuccess={() => { onSuccess={(res) => {
this.setState({ this.setState({
dest: res.dest,
countryCode: res.countryCode,
current: this.state.current + 1, current: this.state.current + 1,
}); });
}} }}
@ -195,7 +197,7 @@ class MfaSetupPage extends React.Component {
); );
case 2: case 2:
return ( return (
<MfaEnableForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes} <MfaEnableForm user={this.getUser()} mfaType={this.state.mfaType} secret={this.state.mfaProps.secret} recoveryCodes={this.state.mfaProps.recoveryCodes} dest={this.state.dest} countryCode={this.state.countryCode}
onSuccess={() => { onSuccess={() => {
Setting.showMessage("success", i18next.t("general:Enabled successfully")); Setting.showMessage("success", i18next.t("general:Enabled successfully"));
this.props.onfinish(); this.props.onfinish();

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import CryptoJS from "crypto-js"; import CryptoJS from "crypto-js";
import i18next from "i18next"; import {Buffer} from "buffer";
export function getRandomKeyForObfuscator(obfuscatorType) { export function getRandomKeyForObfuscator(obfuscatorType) {
if (obfuscatorType === "DES") { if (obfuscatorType === "DES") {
@ -45,17 +45,17 @@ function encrypt(cipher, key, iv, password) {
export function checkPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey) { export function checkPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey) {
if (passwordObfuscatorType === undefined) { if (passwordObfuscatorType === undefined) {
return i18next.t("organization:failed to get password obfuscator"); return "passwordObfuscatorType should not be undefined";
} else if (passwordObfuscatorType === "Plain" || passwordObfuscatorType === "") { } else if (passwordObfuscatorType === "Plain" || passwordObfuscatorType === "") {
return ""; return "";
} else if (passwordObfuscatorType === "AES" || passwordObfuscatorType === "DES") { } else if (passwordObfuscatorType === "AES" || passwordObfuscatorType === "DES") {
if (passwordObfuscatorKeyRegexes[passwordObfuscatorType].test(passwordObfuscatorKey)) { if (passwordObfuscatorKeyRegexes[passwordObfuscatorType].test(passwordObfuscatorKey)) {
return ""; return "";
} else { } else {
return `${i18next.t("organization:The password obfuscator key doesn't match the regex")}: ${passwordObfuscatorKeyRegexes[passwordObfuscatorType].source}`; return `The password obfuscator key doesn't match the regex: ${passwordObfuscatorKeyRegexes[passwordObfuscatorType].source}`;
} }
} else { } else {
return `${i18next.t("organization:unsupported password obfuscator type")}: ${passwordObfuscatorType}`; return `unsupported password obfuscator type: ${passwordObfuscatorType}`;
} }
} }

View File

@ -119,6 +119,10 @@ const authInfo = {
scope: "user_info", scope: "user_info",
endpoint: "https://open.douyin.com/platform/oauth/connect", endpoint: "https://open.douyin.com/platform/oauth/connect",
}, },
Kwai: {
scope: "user_info",
endpoint: "https://open.kuaishou.com/oauth2/connect",
},
Custom: { Custom: {
endpoint: "https://example.com/", endpoint: "https://example.com/",
}, },
@ -470,6 +474,8 @@ export function getAuthUrl(application, provider, method, code) {
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`; return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Douyin" || provider.type === "TikTok") { } else if (provider.type === "Douyin" || provider.type === "TikTok") {
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`; return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Kwai") {
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Custom") { } else if (provider.type === "Custom") {
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`; return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
} else if (provider.type === "Bilibili") { } else if (provider.type === "Bilibili") {

View File

@ -40,6 +40,7 @@ import SteamLoginButton from "./SteamLoginButton";
import BilibiliLoginButton from "./BilibiliLoginButton"; import BilibiliLoginButton from "./BilibiliLoginButton";
import OktaLoginButton from "./OktaLoginButton"; import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton"; import DouyinLoginButton from "./DouyinLoginButton";
import KwaiLoginButton from "./KwaiLoginButton";
import LoginButton from "./LoginButton"; import LoginButton from "./LoginButton";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import {WechatOfficialAccountModal} from "./Util"; import {WechatOfficialAccountModal} from "./Util";
@ -96,6 +97,8 @@ function getSigninButton(provider) {
return <OktaLoginButton text={text} align={"center"} />; return <OktaLoginButton text={text} align={"center"} />;
} else if (provider.type === "Douyin") { } else if (provider.type === "Douyin") {
return <DouyinLoginButton text={text} align={"center"} />; return <DouyinLoginButton text={text} align={"center"} />;
} else if (provider.type === "Kwai") {
return <KwaiLoginButton text={text} align={"center"} />;
} else { } else {
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />; return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
} }

View File

@ -113,6 +113,9 @@ export function getCasLoginParameters(owner, name) {
export function getOAuthGetParameters(params) { export function getOAuthGetParameters(params) {
const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search); const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search);
const lowercaseQueries = {};
queries.forEach((val, key) => {lowercaseQueries[key.toLowerCase()] = val;});
const clientId = getRefinedValue(queries.get("client_id")); const clientId = getRefinedValue(queries.get("client_id"));
const responseType = getRefinedValue(queries.get("response_type")); const responseType = getRefinedValue(queries.get("response_type"));
@ -138,9 +141,9 @@ export function getOAuthGetParameters(params) {
const nonce = getRefinedValue(queries.get("nonce")); const nonce = getRefinedValue(queries.get("nonce"));
const challengeMethod = getRefinedValue(queries.get("code_challenge_method")); const challengeMethod = getRefinedValue(queries.get("code_challenge_method"));
const codeChallenge = getRefinedValue(queries.get("code_challenge")); const codeChallenge = getRefinedValue(queries.get("code_challenge"));
const samlRequest = getRefinedValue(queries.get("SAMLRequest")); const samlRequest = getRefinedValue(lowercaseQueries["samlRequest".toLowerCase()]);
const relayState = getRefinedValue(queries.get("RelayState")); const relayState = getRefinedValue(lowercaseQueries["RelayState".toLowerCase()]);
const noRedirect = getRefinedValue(queries.get("noRedirect")); const noRedirect = getRefinedValue(lowercaseQueries["noRedirect".toLowerCase()]);
if (clientId === "" && samlRequest === "") { if (clientId === "" && samlRequest === "") {
// login // login

View File

@ -3,11 +3,15 @@ import i18next from "i18next";
import React, {useState} from "react"; import React, {useState} from "react";
import * as MfaBackend from "../../backend/MfaBackend"; import * as MfaBackend from "../../backend/MfaBackend";
export function MfaEnableForm({user, mfaType, recoveryCodes, onSuccess, onFail}) { export function MfaEnableForm({user, mfaType, secret, recoveryCodes, dest, countryCode, onSuccess, onFail}) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const requestEnableMfa = () => { const requestEnableMfa = () => {
const data = { const data = {
mfaType, mfaType,
secret,
recoveryCodes,
dest,
countryCode,
...user, ...user,
}; };
setLoading(true); setLoading(true);

View File

@ -26,11 +26,13 @@ export const mfaSetup = "mfaSetup";
export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) { export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const onFinish = ({passcode}) => { const onFinish = ({passcode, countryCode, dest}) => {
const data = {passcode, mfaType: mfaProps.mfaType, ...user}; const data = {passcode, mfaType: mfaProps.mfaType, secret: mfaProps.secret, dest: dest, countryCode: countryCode, ...user};
MfaBackend.MfaSetupVerify(data) MfaBackend.MfaSetupVerify(data)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
res.dest = dest;
res.countryCode = countryCode;
onSuccess(res); onSuccess(res);
} else { } else {
onFail(res); onFail(res);

View File

@ -1,5 +1,5 @@
import {UserOutlined} from "@ant-design/icons"; import {UserOutlined} from "@ant-design/icons";
import {Button, Form, Input} from "antd"; import {Button, Form, Input, Space} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import {CountryCodeSelect} from "../../common/select/CountryCodeSelect"; import {CountryCodeSelect} from "../../common/select/CountryCodeSelect";
@ -15,15 +15,18 @@ export const MfaVerifySmsForm = ({mfaProps, application, onFinish, method, user}
useEffect(() => { useEffect(() => {
if (method === mfaAuth) { if (method === mfaAuth) {
setDest(mfaProps.secret); setDest(mfaProps.secret);
form.setFieldValue("dest", mfaProps.secret);
return; return;
} }
if (mfaProps.mfaType === SmsMfaType) { if (mfaProps.mfaType === SmsMfaType) {
setDest(user.phone); setDest(user.phone);
form.setFieldValue("dest", user.phone);
return; return;
} }
if (mfaProps.mfaType === EmailMfaType) { if (mfaProps.mfaType === EmailMfaType) {
setDest(user.email); setDest(user.email);
form.setFieldValue("dest", user.email);
} }
}, [mfaProps.mfaType]); }, [mfaProps.mfaType]);
@ -57,45 +60,44 @@ export const MfaVerifySmsForm = ({mfaProps, application, onFinish, method, user}
<div style={{marginBottom: 20, textAlign: "left", gap: 8}}> <div style={{marginBottom: 20, textAlign: "left", gap: 8}}>
{isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {dest} {isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {dest}
</div> : </div> :
(<React.Fragment> (
<p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") : <p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") :
i18next.t("mfa:Please bind your phone first, the system automatically uses the phone for multi-factor authentication")} i18next.t("mfa:Please bind your phone first, the system automatically uses the phone for multi-factor authentication")}
</p> </p>
<Input.Group compact style={{width: "300Px", marginBottom: "30px"}}>
{isEmail() ? null :
<Form.Item
name="countryCode"
noStyle
rules={[
{
required: false,
message: i18next.t("signup:Please select your country code!"),
},
]}
>
<CountryCodeSelect
initValue={mfaProps.countryCode}
style={{width: "30%"}}
countryCodes={application.organizationObj.countryCodes}
/>
</Form.Item>
}
<Form.Item
name="dest"
noStyle
rules={[{required: true, message: i18next.t("login:Please input your Email or Phone!")}]}
>
<Input
style={{width: isEmail() ? "100% " : "70%"}}
onChange={(e) => {setDest(e.target.value);}}
prefix={<UserOutlined />}
placeholder={isEmail() ? i18next.t("general:Email") : i18next.t("general:Phone")}
/>
</Form.Item>
</Input.Group>
</React.Fragment>
) )
} }
<Space.Compact style={{width: "300Px", marginBottom: "30px", display: isShowText() ? "none" : ""}}>
{isEmail() || isShowText() ? null :
<Form.Item
name="countryCode"
noStyle
rules={[
{
required: false,
message: i18next.t("signup:Please select your country code!"),
},
]}
>
<CountryCodeSelect
initValue={mfaProps.countryCode}
style={{width: "30%"}}
countryCodes={application.organizationObj.countryCodes}
/>
</Form.Item>
}
<Form.Item
name="dest"
noStyle
rules={[{required: true, message: i18next.t("login:Please input your Email or Phone!")}]}
>
<Input
style={{width: isEmail() ? "100% " : "70%"}}
onChange={(e) => {setDest(e.target.value);}}
prefix={<UserOutlined />}
placeholder={isEmail() ? i18next.t("general:Email") : i18next.t("general:Phone")}
/>
</Form.Item>
</Space.Compact>
<Form.Item <Form.Item
name="passcode" name="passcode"
rules={[{required: true, message: i18next.t("login:Please input your code!")}]} rules={[{required: true, message: i18next.t("login:Please input your code!")}]}

View File

@ -32,6 +32,9 @@ export function MfaSetupVerify(values) {
formData.append("name", values.name); formData.append("name", values.name);
formData.append("mfaType", values.mfaType); formData.append("mfaType", values.mfaType);
formData.append("passcode", values.passcode); formData.append("passcode", values.passcode);
formData.append("secret", values.secret);
formData.append("dest", values.dest);
formData.append("countryCode", values.countryCode);
return fetch(`${Setting.ServerUrl}/api/mfa/setup/verify`, { return fetch(`${Setting.ServerUrl}/api/mfa/setup/verify`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
@ -44,6 +47,10 @@ export function MfaSetupEnable(values) {
formData.append("mfaType", values.mfaType); formData.append("mfaType", values.mfaType);
formData.append("owner", values.owner); formData.append("owner", values.owner);
formData.append("name", values.name); formData.append("name", values.name);
formData.append("secret", values.secret);
formData.append("recoveryCodes", values.recoveryCodes);
formData.append("dest", values.dest);
formData.append("countryCode", values.countryCode);
return fetch(`${Setting.ServerUrl}/api/mfa/setup/enable`, { return fetch(`${Setting.ServerUrl}/api/mfa/setup/enable`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",

View File

@ -135,6 +135,15 @@ const Dashboard = (props) => {
i18next.t("general:Applications"), i18next.t("general:Applications"),
i18next.t("general:Organizations"), i18next.t("general:Organizations"),
i18next.t("general:Subscriptions"), i18next.t("general:Subscriptions"),
i18next.t("general:Roles"),
i18next.t("general:Groups"),
i18next.t("general:Resources"),
i18next.t("general:Certs"),
i18next.t("general:Permissions"),
i18next.t("general:Transactions"),
i18next.t("general:Models"),
i18next.t("general:Adapters"),
i18next.t("general:Enforcers"),
], top: "10%"}, ], top: "10%"},
grid: {left: "3%", right: "4%", bottom: "0", top: "25%", containLabel: true}, grid: {left: "3%", right: "4%", bottom: "0", top: "25%", containLabel: true},
xAxis: {type: "category", boundaryGap: false, data: dateArray}, xAxis: {type: "category", boundaryGap: false, data: dateArray},
@ -145,6 +154,15 @@ const Dashboard = (props) => {
{name: i18next.t("general:Providers"), type: "line", data: dashboardData.providerCounts}, {name: i18next.t("general:Providers"), type: "line", data: dashboardData.providerCounts},
{name: i18next.t("general:Applications"), type: "line", data: dashboardData.applicationCounts}, {name: i18next.t("general:Applications"), type: "line", data: dashboardData.applicationCounts},
{name: i18next.t("general:Subscriptions"), type: "line", data: dashboardData.subscriptionCounts}, {name: i18next.t("general:Subscriptions"), type: "line", data: dashboardData.subscriptionCounts},
{name: i18next.t("general:Roles"), type: "line", data: dashboardData.roleCounts},
{name: i18next.t("general:Groups"), type: "line", data: dashboardData.groupCounts},
{name: i18next.t("general:Resources"), type: "line", data: dashboardData.resourceCounts},
{name: i18next.t("general:Certs"), type: "line", data: dashboardData.certCounts},
{name: i18next.t("general:Permissions"), type: "line", data: dashboardData.permissionCounts},
{name: i18next.t("general:Transactions"), type: "line", data: dashboardData.transactionCounts},
{name: i18next.t("general:Models"), type: "line", data: dashboardData.modelCounts},
{name: i18next.t("general:Adapters"), type: "line", data: dashboardData.adapterCounts},
{name: i18next.t("general:Enforcers"), type: "line", data: dashboardData.enforcerCounts},
], ],
}; };
myChart.setOption(option); myChart.setOption(option);

View File

@ -0,0 +1,113 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Alert, Button, QRCode} from "antd";
import copy from "copy-to-clipboard";
import * as Setting from "../Setting";
import i18next from "i18next";
export const generateCasdoorAppUrl = (accessToken, forQrCode = true) => {
let qrUrl = "";
let error = null;
if (!accessToken) {
error = i18next.t("general:Access token is empty");
return {qrUrl, error};
}
qrUrl = `casdoor-app://login?serverUrl=${window.location.origin}&accessToken=${accessToken}`;
if (forQrCode && qrUrl.length >= 2000) {
qrUrl = "";
error = i18next.t("general:QR code is too large");
}
return {qrUrl, error};
};
export const CasdoorAppQrCode = ({accessToken, icon}) => {
const {qrUrl, error} = generateCasdoorAppUrl(accessToken, true);
if (error) {
return <Alert message={error} type="error" showIcon />;
}
return (
<QRCode
value={qrUrl}
icon={icon}
errorLevel="M"
size={230}
bordered={false}
/>
);
};
export const CasdoorAppUrl = ({accessToken}) => {
const {qrUrl, error} = generateCasdoorAppUrl(accessToken, false);
const handleCopyUrl = async() => {
if (!window.isSecureContext) {
return;
}
copy(qrUrl);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
};
if (error) {
return <Alert message={error} type="error" showIcon />;
}
return (
<div>
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "10px",
}}>
{window.isSecureContext && (
<Button size="small" type="primary" onClick={handleCopyUrl} style={{marginLeft: "10px"}}>
{i18next.t("resource:Copy Link")}
</Button>
)}
</div>
<div
style={{
padding: "10px",
maxWidth: "400px",
maxHeight: "100px",
overflow: "auto",
wordBreak: "break-all",
whiteSpace: "pre-wrap",
cursor: "pointer",
userSelect: "all",
backgroundColor: "#f5f5f5",
borderRadius: "4px",
}}
onClick={(e) => {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(e.target);
selection.removeAllRanges();
selection.addRange(range);
}}
>
{qrUrl}
</div>
</div>
);
};

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Odesílání", "Sending": "Odesílání",
"Submit and complete": "Odeslat a dokončit" "Submit and complete": "Odeslat a dokončit"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Upravit Enforcer", "Edit Enforcer": "Upravit Enforcer",
"New Enforcer": "Nový Enforcer" "New Enforcer": "Nový Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Přejít na zapisovatelnou demo stránku?", "Go to writable demo site?": "Přejít na zapisovatelnou demo stránku?",
"Groups": "Skupiny", "Groups": "Skupiny",
"Groups - Tooltip": "Skupiny - Tooltip", "Groups - Tooltip": "Skupiny - Tooltip",
"Hide password": "Hide password",
"Home": "Domů", "Home": "Domů",
"Home - Tooltip": "Domovská stránka aplikace", "Home - Tooltip": "Domovská stránka aplikace",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unikátní náhodný řetězec", "ID - Tooltip": "Unikátní náhodný řetězec",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identita", "Identity": "Identita",
"Invitations": "Pozvánky", "Invitations": "Pozvánky",
"Is enabled": "Je povoleno", "Is enabled": "Je povoleno",
@ -312,6 +327,10 @@
"Password - Tooltip": "Ujistěte se, že heslo je správné", "Password - Tooltip": "Ujistěte se, že heslo je správné",
"Password complexity options": "Možnosti složitosti hesla", "Password complexity options": "Možnosti složitosti hesla",
"Password complexity options - Tooltip": "Různé kombinace možností složitosti hesla", "Password complexity options - Tooltip": "Různé kombinace možností složitosti hesla",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Heslová sůl", "Password salt": "Heslová sůl",
"Password salt - Tooltip": "Náhodný parametr použitý pro šifrování hesla", "Password salt - Tooltip": "Náhodný parametr použitý pro šifrování hesla",
"Password type": "Typ hesla", "Password type": "Typ hesla",
@ -559,7 +578,6 @@
"Use SMS": "Použít SMS", "Use SMS": "Použít SMS",
"Use SMS verification code": "Použít ověřovací kód SMS", "Use SMS verification code": "Použít ověřovací kód SMS",
"Use a recovery code": "Použít obnovovací kód", "Use a recovery code": "Použít obnovovací kód",
"Verification failed": "Ověření selhalo",
"Verify Code": "Ověřit kód", "Verify Code": "Ověřit kód",
"Verify Password": "Ověřit heslo", "Verify Password": "Ověřit heslo",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Pokročilý editor",
"Basic Editor": "Základní editor",
"Edit Model": "Upravit model", "Edit Model": "Upravit model",
"Model text": "Text modelu", "Model text": "Text modelu",
"Model text - Tooltip": "Casbin model řízení přístupu, včetně vestavěných modelů jako ACL, RBAC, ABAC, RESTful, atd. Můžete také vytvářet vlastní modely. Pro více informací navštivte webové stránky Casbin", "Model text - Tooltip": "Casbin model řízení přístupu, včetně vestavěných modelů jako ACL, RBAC, ABAC, RESTful, atd. Můžete také vytvářet vlastní modely. Pro více informací navštivte webové stránky Casbin",
@ -592,6 +612,8 @@
"Modify rule": "Upravit pravidlo", "Modify rule": "Upravit pravidlo",
"New Organization": "Nová organizace", "New Organization": "Nová organizace",
"Optional": "Volitelný", "Optional": "Volitelný",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Výzva", "Prompt": "Výzva",
"Required": "Povinné", "Required": "Povinné",
"Soft deletion": "Měkké smazání", "Soft deletion": "Měkké smazání",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Koupit", "Buy": "Koupit",
"Buy Product": "Koupit produkt", "Buy Product": "Koupit produkt",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail produktu", "Detail - Tooltip": "Detail produktu",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Testovací stránka nákupu..", "Test buy page..": "Testovací stránka nákupu..",
"There is no payment channel for this product.": "Pro tento produkt neexistuje žádný platební kanál.", "There is no payment channel for this product.": "Pro tento produkt neexistuje žádný platební kanál.",
"This product is currently not in sale.": "Tento produkt není momentálně v prodeji.", "This product is currently not in sale.": "Tento produkt není momentálně v prodeji.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Upravit HTML pro registraci", "Signup HTML - Edit": "Upravit HTML pro registraci",
"Signup HTML - Tooltip": "Vlastní HTML pro nahrazení výchozího stylu registrační stránky", "Signup HTML - Tooltip": "Vlastní HTML pro nahrazení výchozího stylu registrační stránky",
"Signup group": "Skupina pro registraci", "Signup group": "Skupina pro registraci",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Tiché", "Silent": "Tiché",
"Site key": "Klíč stránky", "Site key": "Klíč stránky",
"Site key - Tooltip": "Nápověda ke klíči stránky", "Site key - Tooltip": "Nápověda ke klíči stránky",
@ -1157,6 +1178,7 @@
"Keys": "Klíče", "Keys": "Klíče",
"Language": "Jazyk", "Language": "Jazyk",
"Language - Tooltip": "Jazyk - Nápověda", "Language - Tooltip": "Jazyk - Nápověda",
"Last change password time": "Last change password time",
"Link": "Odkaz", "Link": "Odkaz",
"Location": "Místo", "Location": "Místo",
"Location - Tooltip": "Město bydliště", "Location - Tooltip": "Město bydliště",

View File

@ -161,6 +161,18 @@
"Sending": "Sendet", "Sending": "Sendet",
"Submit and complete": "Einreichen und abschließen" "Submit and complete": "Einreichen und abschließen"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Gehe zur beschreibbaren Demo-Website?", "Go to writable demo site?": "Gehe zur beschreibbaren Demo-Website?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Zuhause", "Home": "Zuhause",
"Home - Tooltip": "Homepage der Anwendung", "Home - Tooltip": "Homepage der Anwendung",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Einzigartiger Zufallsstring", "ID - Tooltip": "Einzigartiger Zufallsstring",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Ist aktiviert", "Is enabled": "Ist aktiviert",
@ -312,6 +327,10 @@
"Password - Tooltip": "Stellen Sie sicher, dass das Passwort korrekt ist", "Password - Tooltip": "Stellen Sie sicher, dass das Passwort korrekt ist",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Password complexity options - Tooltip", "Password complexity options - Tooltip": "Password complexity options - Tooltip",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Passwort-Salt", "Password salt": "Passwort-Salt",
"Password salt - Tooltip": "Zufälliger Parameter, der für die Verschlüsselung von Passwörtern verwendet wird", "Password salt - Tooltip": "Zufälliger Parameter, der für die Verschlüsselung von Passwörtern verwendet wird",
"Password type": "Passworttyp", "Password type": "Passworttyp",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Erweiterter Editor",
"Basic Editor": "Basis-Editor",
"Edit Model": "Modell bearbeiten", "Edit Model": "Modell bearbeiten",
"Model text": "Modelltext", "Model text": "Modelltext",
"Model text - Tooltip": "Casbin Zugriffskontrollmodell inklusive integrierter Modelle wie ACL, RBAC, ABAC, RESTful, usw. Sie können auch benutzerdefinierte Modelle erstellen. Weitere Informationen finden Sie auf der Casbin-Website", "Model text - Tooltip": "Casbin Zugriffskontrollmodell inklusive integrierter Modelle wie ACL, RBAC, ABAC, RESTful, usw. Sie können auch benutzerdefinierte Modelle erstellen. Weitere Informationen finden Sie auf der Casbin-Website",
@ -592,6 +612,8 @@
"Modify rule": "Regel ändern", "Modify rule": "Regel ändern",
"New Organization": "Neue Organisation", "New Organization": "Neue Organisation",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Softe Löschung", "Soft deletion": "Softe Löschung",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Kaufen", "Buy": "Kaufen",
"Buy Product": "Produkt kaufen", "Buy Product": "Produkt kaufen",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail des Produkts", "Detail - Tooltip": "Detail des Produkts",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Testkaufseite.", "Test buy page..": "Testkaufseite.",
"There is no payment channel for this product.": "Es gibt keinen Zahlungskanal für dieses Produkt.", "There is no payment channel for this product.": "Es gibt keinen Zahlungskanal für dieses Produkt.",
"This product is currently not in sale.": "Dieses Produkt steht derzeit nicht zum Verkauf.", "This product is currently not in sale.": "Dieses Produkt steht derzeit nicht zum Verkauf.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Registrierung HTML - Bearbeiten", "Signup HTML - Edit": "Registrierung HTML - Bearbeiten",
"Signup HTML - Tooltip": "Benutzerdefiniertes HTML zur Ersetzung des Standard-Registrierungs-Seitenstils", "Signup HTML - Tooltip": "Benutzerdefiniertes HTML zur Ersetzung des Standard-Registrierungs-Seitenstils",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site-Key", "Site key": "Site-Key",
"Site key - Tooltip": "Seitenschlüssel", "Site key - Tooltip": "Seitenschlüssel",
@ -1157,6 +1178,7 @@
"Keys": "Schlüssel", "Keys": "Schlüssel",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Ort", "Location": "Ort",
"Location - Tooltip": "Stadt des Wohnsitzes", "Location - Tooltip": "Stadt des Wohnsitzes",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Envío", "Sending": "Envío",
"Submit and complete": "Enviar y completar" "Submit and complete": "Enviar y completar"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "¿Ir al sitio demo editable?", "Go to writable demo site?": "¿Ir al sitio demo editable?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Hogar", "Home": "Hogar",
"Home - Tooltip": "Página de inicio de la aplicación", "Home - Tooltip": "Página de inicio de la aplicación",
"ID": "identificación", "ID": "identificación",
"ID - Tooltip": "Cadena aleatoria única", "ID - Tooltip": "Cadena aleatoria única",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Está habilitado", "Is enabled": "Está habilitado",
@ -312,6 +327,10 @@
"Password - Tooltip": "Asegúrate de que la contraseña sea correcta", "Password - Tooltip": "Asegúrate de que la contraseña sea correcta",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Password complexity options - Tooltip", "Password complexity options - Tooltip": "Password complexity options - Tooltip",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Sal de contraseña", "Password salt": "Sal de contraseña",
"Password salt - Tooltip": "Parámetro aleatorio utilizado para la encriptación de contraseñas", "Password salt - Tooltip": "Parámetro aleatorio utilizado para la encriptación de contraseñas",
"Password type": "Tipo de contraseña", "Password type": "Tipo de contraseña",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Editor avanzado",
"Basic Editor": "Editor básico",
"Edit Model": "Editar modelo", "Edit Model": "Editar modelo",
"Model text": "Texto modelo", "Model text": "Texto modelo",
"Model text - Tooltip": "Modelo de control de acceso Casbin, incluyendo modelos integrados como ACL, RBAC, ABAC, RESTful, etc. También puede crear modelos personalizados. Para obtener más información, visite el sitio web de Casbin", "Model text - Tooltip": "Modelo de control de acceso Casbin, incluyendo modelos integrados como ACL, RBAC, ABAC, RESTful, etc. También puede crear modelos personalizados. Para obtener más información, visite el sitio web de Casbin",
@ -592,6 +612,8 @@
"Modify rule": "Modificar regla", "Modify rule": "Modificar regla",
"New Organization": "Nueva organización", "New Organization": "Nueva organización",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Eliminación suave", "Soft deletion": "Eliminación suave",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Comprar", "Buy": "Comprar",
"Buy Product": "Comprar producto", "Buy Product": "Comprar producto",
"CNY": "Año Nuevo Chino (ANC)",
"Detail": "Detalle", "Detail": "Detalle",
"Detail - Tooltip": "Detalle del producto", "Detail - Tooltip": "Detalle del producto",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Página de compra de prueba.", "Test buy page..": "Página de compra de prueba.",
"There is no payment channel for this product.": "No hay canal de pago para este producto.", "There is no payment channel for this product.": "No hay canal de pago para este producto.",
"This product is currently not in sale.": "Este producto actualmente no está a la venta.", "This product is currently not in sale.": "Este producto actualmente no está a la venta.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Registro HTML - Editar", "Signup HTML - Edit": "Registro HTML - Editar",
"Signup HTML - Tooltip": "HTML personalizado para reemplazar el estilo predeterminado de la página de registro", "Signup HTML - Tooltip": "HTML personalizado para reemplazar el estilo predeterminado de la página de registro",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Clave del sitio", "Site key": "Clave del sitio",
"Site key - Tooltip": "Clave del sitio", "Site key - Tooltip": "Clave del sitio",
@ -1157,6 +1178,7 @@
"Keys": "Claves", "Keys": "Claves",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Enlace", "Link": "Enlace",
"Location": "Ubicación", "Location": "Ubicación",
"Location - Tooltip": "Ciudad de residencia", "Location - Tooltip": "Ciudad de residencia",

File diff suppressed because it is too large Load Diff

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Envoi en cours", "Sending": "Envoi en cours",
"Submit and complete": "Soumettre et compléter" "Submit and complete": "Soumettre et compléter"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Éditer l'exécuteur", "Edit Enforcer": "Éditer l'exécuteur",
"New Enforcer": "Ajouter un exécuteur" "New Enforcer": "Ajouter un exécuteur"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Allez sur le site de démonstration modifiable ?", "Go to writable demo site?": "Allez sur le site de démonstration modifiable ?",
"Groups": "Groupes", "Groups": "Groupes",
"Groups - Tooltip": "Groupes - infobulle", "Groups - Tooltip": "Groupes - infobulle",
"Hide password": "Hide password",
"Home": "Accueil", "Home": "Accueil",
"Home - Tooltip": "Page d'accueil de l'application", "Home - Tooltip": "Page d'accueil de l'application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Chaîne unique aléatoire", "ID - Tooltip": "Chaîne unique aléatoire",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identité", "Identity": "Identité",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Est activé", "Is enabled": "Est activé",
@ -312,6 +327,10 @@
"Password - Tooltip": "Assurez-vous que le mot de passe soit correct", "Password - Tooltip": "Assurez-vous que le mot de passe soit correct",
"Password complexity options": "Options de complexité du mot de passe", "Password complexity options": "Options de complexité du mot de passe",
"Password complexity options - Tooltip": "Différentes combinaisons d'options de complexité de mot de passe", "Password complexity options - Tooltip": "Différentes combinaisons d'options de complexité de mot de passe",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Sel de mot de passe", "Password salt": "Sel de mot de passe",
"Password salt - Tooltip": "Paramètre aléatoire utilisé pour le chiffrement des mots de passe", "Password salt - Tooltip": "Paramètre aléatoire utilisé pour le chiffrement des mots de passe",
"Password type": "Type de mot de passe", "Password type": "Type de mot de passe",
@ -559,7 +578,6 @@
"Use SMS": "Utiliser les SMS", "Use SMS": "Utiliser les SMS",
"Use SMS verification code": "Utiliser la vérification par code SMS", "Use SMS verification code": "Utiliser la vérification par code SMS",
"Use a recovery code": "Utiliser un code de récupération", "Use a recovery code": "Utiliser un code de récupération",
"Verification failed": "Échec de la vérification",
"Verify Code": "Vérifier le code", "Verify Code": "Vérifier le code",
"Verify Password": "Confirmez le mot de passe", "Verify Password": "Confirmez le mot de passe",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Éditeur avancé",
"Basic Editor": "Éditeur de base",
"Edit Model": "Modifier le modèle", "Edit Model": "Modifier le modèle",
"Model text": "Définition du modèle", "Model text": "Définition du modèle",
"Model text - Tooltip": "Modèle de contrôle d'accès Casbin, comprenant des modèles intégrés tels que ACL, RBAC, ABAC, RESTful, etc. Vous pouvez également créer des modèles personnalisés. Pour plus d'informations, veuillez visiter le site web de Casbin", "Model text - Tooltip": "Modèle de contrôle d'accès Casbin, comprenant des modèles intégrés tels que ACL, RBAC, ABAC, RESTful, etc. Vous pouvez également créer des modèles personnalisés. Pour plus d'informations, veuillez visiter le site web de Casbin",
@ -592,6 +612,8 @@
"Modify rule": "Règle de modification", "Modify rule": "Règle de modification",
"New Organization": "Nouvelle organisation", "New Organization": "Nouvelle organisation",
"Optional": "Optionnel", "Optional": "Optionnel",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Requis", "Required": "Requis",
"Soft deletion": "Suppression douce", "Soft deletion": "Suppression douce",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Acheter", "Buy": "Acheter",
"Buy Product": "Acheter un produit", "Buy Product": "Acheter un produit",
"CNY": "CNY",
"Detail": "Détail", "Detail": "Détail",
"Detail - Tooltip": "Détail du produit", "Detail - Tooltip": "Détail du produit",
"Dummy": "Exemple", "Dummy": "Exemple",
@ -740,7 +761,6 @@
"Test buy page..": "Page d'achat de test.", "Test buy page..": "Page d'achat de test.",
"There is no payment channel for this product.": "Il n'y a aucun canal de paiement pour ce produit.", "There is no payment channel for this product.": "Il n'y a aucun canal de paiement pour ce produit.",
"This product is currently not in sale.": "Ce produit n'est actuellement pas en vente.", "This product is currently not in sale.": "Ce produit n'est actuellement pas en vente.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "HTML de la page d'inscription - Modifier", "Signup HTML - Edit": "HTML de la page d'inscription - Modifier",
"Signup HTML - Tooltip": "HTML personnalisé pour remplacer le style par défaut de la page d'inscription", "Signup HTML - Tooltip": "HTML personnalisé pour remplacer le style par défaut de la page d'inscription",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silencieux", "Silent": "Silencieux",
"Site key": "Clé de site", "Site key": "Clé de site",
"Site key - Tooltip": "Clé de site", "Site key - Tooltip": "Clé de site",
@ -1157,6 +1178,7 @@
"Keys": "Clés", "Keys": "Clés",
"Language": "Langue", "Language": "Langue",
"Language - Tooltip": "Langue - Infobulle", "Language - Tooltip": "Langue - Infobulle",
"Last change password time": "Last change password time",
"Link": "Lier", "Link": "Lier",
"Location": "Localisation", "Location": "Localisation",
"Location - Tooltip": "Ville de résidence", "Location - Tooltip": "Ville de résidence",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Mengirimkan", "Sending": "Mengirimkan",
"Submit and complete": "Kirim dan selesaikan" "Submit and complete": "Kirim dan selesaikan"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Pergi ke situs demo yang dapat ditulis?", "Go to writable demo site?": "Pergi ke situs demo yang dapat ditulis?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Rumah", "Home": "Rumah",
"Home - Tooltip": "Halaman utama aplikasi", "Home - Tooltip": "Halaman utama aplikasi",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Karakter acak unik", "ID - Tooltip": "Karakter acak unik",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Diaktifkan", "Is enabled": "Diaktifkan",
@ -312,6 +327,10 @@
"Password - Tooltip": "Pastikan kata sandi yang benar", "Password - Tooltip": "Pastikan kata sandi yang benar",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Password complexity options - Tooltip", "Password complexity options - Tooltip": "Password complexity options - Tooltip",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Garam sandi", "Password salt": "Garam sandi",
"Password salt - Tooltip": "Parameter acak yang digunakan untuk enkripsi kata sandi", "Password salt - Tooltip": "Parameter acak yang digunakan untuk enkripsi kata sandi",
"Password type": "Jenis kata sandi", "Password type": "Jenis kata sandi",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Editor lanjutan",
"Basic Editor": "Editor dasar",
"Edit Model": "Mengedit Model", "Edit Model": "Mengedit Model",
"Model text": "Teks Model", "Model text": "Teks Model",
"Model text - Tooltip": "Model kontrol akses Casbin, termasuk model bawaan seperti ACL, RBAC, ABAC, RESTful, dll. Anda juga dapat membuat model kustom. Untuk informasi lebih lanjut, silakan kunjungi situs web Casbin", "Model text - Tooltip": "Model kontrol akses Casbin, termasuk model bawaan seperti ACL, RBAC, ABAC, RESTful, dll. Anda juga dapat membuat model kustom. Untuk informasi lebih lanjut, silakan kunjungi situs web Casbin",
@ -592,6 +612,8 @@
"Modify rule": "Mengubah aturan", "Modify rule": "Mengubah aturan",
"New Organization": "Organisasi baru", "New Organization": "Organisasi baru",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Penghapusan lunak", "Soft deletion": "Penghapusan lunak",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Beli", "Buy": "Beli",
"Buy Product": "Beli Produk", "Buy Product": "Beli Produk",
"CNY": "CNY",
"Detail": "Rincian", "Detail": "Rincian",
"Detail - Tooltip": "Detail produk", "Detail - Tooltip": "Detail produk",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Halaman pembelian uji coba.", "Test buy page..": "Halaman pembelian uji coba.",
"There is no payment channel for this product.": "Tidak ada saluran pembayaran untuk produk ini.", "There is no payment channel for this product.": "Tidak ada saluran pembayaran untuk produk ini.",
"This product is currently not in sale.": "Produk ini saat ini tidak dijual.", "This product is currently not in sale.": "Produk ini saat ini tidak dijual.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Pendaftaran HTML - Sunting", "Signup HTML - Edit": "Pendaftaran HTML - Sunting",
"Signup HTML - Tooltip": "HTML khusus untuk mengganti gaya halaman pendaftaran bawaan", "Signup HTML - Tooltip": "HTML khusus untuk mengganti gaya halaman pendaftaran bawaan",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Kunci situs", "Site key": "Kunci situs",
"Site key - Tooltip": "Kunci situs atau kunci halaman web", "Site key - Tooltip": "Kunci situs atau kunci halaman web",
@ -1157,6 +1178,7 @@
"Keys": "Kunci", "Keys": "Kunci",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Tautan", "Link": "Tautan",
"Location": "Lokasi", "Location": "Lokasi",
"Location - Tooltip": "Kota tempat tinggal", "Location - Tooltip": "Kota tempat tinggal",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "送信", "Sending": "送信",
"Submit and complete": "提出して完了してください" "Submit and complete": "提出して完了してください"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "書き込み可能なデモサイトに移動しますか?", "Go to writable demo site?": "書き込み可能なデモサイトに移動しますか?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "ホーム", "Home": "ホーム",
"Home - Tooltip": "アプリケーションのホームページ", "Home - Tooltip": "アプリケーションのホームページ",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "ユニークなランダム文字列", "ID - Tooltip": "ユニークなランダム文字列",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "可能になっています", "Is enabled": "可能になっています",
@ -312,6 +327,10 @@
"Password - Tooltip": "パスワードが正しいことを確認してください", "Password - Tooltip": "パスワードが正しいことを確認してください",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Password complexity options - Tooltip", "Password complexity options - Tooltip": "Password complexity options - Tooltip",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "パスワードのソルト", "Password salt": "パスワードのソルト",
"Password salt - Tooltip": "ランダムパラメーターは、パスワードの暗号化に使用されます", "Password salt - Tooltip": "ランダムパラメーターは、パスワードの暗号化に使用されます",
"Password type": "パスワードタイプ", "Password type": "パスワードタイプ",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "編集モデル", "Edit Model": "編集モデル",
"Model text": "モデルテキスト", "Model text": "モデルテキスト",
"Model text - Tooltip": "Casbinのアクセス制御モデルには、ACL、RBAC、ABAC、RESTfulなどの組み込みモデルが含まれています。カスタムモデルも作成できます。詳細については、Casbinのウェブサイトをご覧ください", "Model text - Tooltip": "Casbinのアクセス制御モデルには、ACL、RBAC、ABAC、RESTfulなどの組み込みモデルが含まれています。カスタムモデルも作成できます。詳細については、Casbinのウェブサイトをご覧ください",
@ -592,6 +612,8 @@
"Modify rule": "ルールを変更する", "Modify rule": "ルールを変更する",
"New Organization": "新しい組織", "New Organization": "新しい組織",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "ソフト削除", "Soft deletion": "ソフト削除",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "購入", "Buy": "購入",
"Buy Product": "製品を購入する", "Buy Product": "製品を購入する",
"CNY": "CNY",
"Detail": "詳細", "Detail": "詳細",
"Detail - Tooltip": "製品の詳細", "Detail - Tooltip": "製品の詳細",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "テスト購入ページ。", "Test buy page..": "テスト購入ページ。",
"There is no payment channel for this product.": "この製品には支払いチャネルがありません。", "There is no payment channel for this product.": "この製品には支払いチャネルがありません。",
"This product is currently not in sale.": "この製品は現在販売されていません。", "This product is currently not in sale.": "この製品は現在販売されていません。",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "サインアップ HTML - 編集", "Signup HTML - Edit": "サインアップ HTML - 編集",
"Signup HTML - Tooltip": "デフォルトのサインアップページスタイルを置き換えるためのカスタムHTML", "Signup HTML - Tooltip": "デフォルトのサインアップページスタイルを置き換えるためのカスタムHTML",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "サイトキー", "Site key": "サイトキー",
"Site key - Tooltip": "サイトキー", "Site key - Tooltip": "サイトキー",
@ -1157,6 +1178,7 @@
"Keys": "鍵", "Keys": "鍵",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "リンク", "Link": "リンク",
"Location": "場所", "Location": "場所",
"Location - Tooltip": "居住都市", "Location - Tooltip": "居住都市",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "전송하기", "Sending": "전송하기",
"Submit and complete": "제출하고 완료하십시오" "Submit and complete": "제출하고 완료하십시오"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "쓰기 가능한 데모 사이트로 이동하시겠습니까?", "Go to writable demo site?": "쓰기 가능한 데모 사이트로 이동하시겠습니까?",
"Groups": "그룹", "Groups": "그룹",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "집", "Home": "집",
"Home - Tooltip": "어플리케이션 홈 페이지", "Home - Tooltip": "어플리케이션 홈 페이지",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "유일한 랜덤 문자열", "ID - Tooltip": "유일한 랜덤 문자열",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "활성화됩니다", "Is enabled": "활성화됩니다",
@ -312,6 +327,10 @@
"Password - Tooltip": "비밀번호가 올바른지 확인하세요", "Password - Tooltip": "비밀번호가 올바른지 확인하세요",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Password complexity options - Tooltip", "Password complexity options - Tooltip": "Password complexity options - Tooltip",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "비밀번호 솔트", "Password salt": "비밀번호 솔트",
"Password salt - Tooltip": "암호화에 사용되는 임의 매개변수", "Password salt - Tooltip": "암호화에 사용되는 임의 매개변수",
"Password type": "암호 유형", "Password type": "암호 유형",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "고급 편집기",
"Basic Editor": "기본 편집기",
"Edit Model": "편집 형태 모델", "Edit Model": "편집 형태 모델",
"Model text": "모델 텍스트", "Model text": "모델 텍스트",
"Model text - Tooltip": "Casbin 액세스 제어 모델은 ACL, RBAC, ABAC, RESTful 등의 내장된 모델을 포함하며 사용자 정의 모델도 만들 수 있습니다. 자세한 정보는 Casbin 웹 사이트를 방문하십시오", "Model text - Tooltip": "Casbin 액세스 제어 모델은 ACL, RBAC, ABAC, RESTful 등의 내장된 모델을 포함하며 사용자 정의 모델도 만들 수 있습니다. 자세한 정보는 Casbin 웹 사이트를 방문하십시오",
@ -592,6 +612,8 @@
"Modify rule": "규칙 수정", "Modify rule": "규칙 수정",
"New Organization": "새로운 조직", "New Organization": "새로운 조직",
"Optional": "선택사항", "Optional": "선택사항",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "소프트 삭제", "Soft deletion": "소프트 삭제",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "구매하다", "Buy": "구매하다",
"Buy Product": "제품을 구입하세요", "Buy Product": "제품을 구입하세요",
"CNY": "CNY",
"Detail": "세부사항", "Detail": "세부사항",
"Detail - Tooltip": "제품의 세부사항", "Detail - Tooltip": "제품의 세부사항",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "시험 구매 페이지.", "Test buy page..": "시험 구매 페이지.",
"There is no payment channel for this product.": "이 제품에 대한 결제 채널이 없습니다.", "There is no payment channel for this product.": "이 제품에 대한 결제 채널이 없습니다.",
"This product is currently not in sale.": "이 제품은 현재 판매되지 않고 있습니다.", "This product is currently not in sale.": "이 제품은 현재 판매되지 않고 있습니다.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "가입 HTML - 수정", "Signup HTML - Edit": "가입 HTML - 수정",
"Signup HTML - Tooltip": "기본 가입 페이지 스타일을 바꾸기 위한 사용자 지정 HTML", "Signup HTML - Tooltip": "기본 가입 페이지 스타일을 바꾸기 위한 사용자 지정 HTML",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "사이트 키", "Site key": "사이트 키",
"Site key - Tooltip": "사이트 키", "Site key - Tooltip": "사이트 키",
@ -1157,6 +1178,7 @@
"Keys": "열쇠", "Keys": "열쇠",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "링크", "Link": "링크",
"Location": "장소", "Location": "장소",
"Location - Tooltip": "거주 도시", "Location - Tooltip": "거주 도시",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Sending", "Sending": "Sending",
"Submit and complete": "Submit and complete" "Submit and complete": "Submit and complete"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Edit Enforcer", "Edit Enforcer": "Edit Enforcer",
"New Enforcer": "New Enforcer" "New Enforcer": "New Enforcer"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Go to writable demo site?", "Go to writable demo site?": "Go to writable demo site?",
"Groups": "Groups", "Groups": "Groups",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Home", "Home": "Home",
"Home - Tooltip": "Home page of the application", "Home - Tooltip": "Home page of the application",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "Unique random string", "ID - Tooltip": "Unique random string",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identity", "Identity": "Identity",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Is enabled", "Is enabled": "Is enabled",
@ -312,6 +327,10 @@
"Password - Tooltip": "Make sure the password is correct", "Password - Tooltip": "Make sure the password is correct",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Different combinations of password complexity options", "Password complexity options - Tooltip": "Different combinations of password complexity options",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Password salt", "Password salt": "Password salt",
"Password salt - Tooltip": "Random parameter used for password encryption", "Password salt - Tooltip": "Random parameter used for password encryption",
"Password type": "Password type", "Password type": "Password type",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website", "Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
@ -592,6 +612,8 @@
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy", "Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail of product", "Detail - Tooltip": "Detail of product",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Test buy page..", "Test buy page..": "Test buy page..",
"There is no payment channel for this product.": "There is no payment channel for this product.", "There is no payment channel for this product.": "There is no payment channel for this product.",
"This product is currently not in sale.": "This product is currently not in sale.", "This product is currently not in sale.": "This product is currently not in sale.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Signup HTML - Edit", "Signup HTML - Edit": "Signup HTML - Edit",
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style", "Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silent", "Silent": "Silent",
"Site key": "Site key", "Site key": "Site key",
"Site key - Tooltip": "Site key", "Site key - Tooltip": "Site key",
@ -1157,6 +1178,7 @@
"Keys": "Keys", "Keys": "Keys",
"Language": "Language", "Language": "Language",
"Language - Tooltip": "Language - Tooltip", "Language - Tooltip": "Language - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "City of residence", "Location - Tooltip": "City of residence",

View File

@ -161,6 +161,18 @@
"Sending": "Enviando", "Sending": "Enviando",
"Submit and complete": "Enviar e concluir" "Submit and complete": "Enviar e concluir"
}, },
"currency": {
"AUD": "AUD",
"CAD": "CAD",
"CHF": "CHF",
"CNY": "CNY",
"EUR": "EUR",
"GBP": "GBP",
"HKD": "HKD",
"JPY": "JPY",
"SGD": "SGD",
"USD": "USD"
},
"enforcer": { "enforcer": {
"Edit Enforcer": "Editar Executor", "Edit Enforcer": "Editar Executor",
"New Enforcer": "Novo Executor" "New Enforcer": "Novo Executor"
@ -266,10 +278,13 @@
"Go to writable demo site?": "Acessar o site de demonstração gravável?", "Go to writable demo site?": "Acessar o site de demonstração gravável?",
"Groups": "Grupos", "Groups": "Grupos",
"Groups - Tooltip": "Groups - Tooltip", "Groups - Tooltip": "Groups - Tooltip",
"Hide password": "Hide password",
"Home": "Página Inicial", "Home": "Página Inicial",
"Home - Tooltip": "Página inicial do aplicativo", "Home - Tooltip": "Página inicial do aplicativo",
"ID": "ID", "ID": "ID",
"ID - Tooltip": "String única aleatória", "ID - Tooltip": "String única aleatória",
"IP whitelist": "IP whitelist",
"IP whitelist - Tooltip": "IP whitelist - Tooltip",
"Identity": "Identidade", "Identity": "Identidade",
"Invitations": "Invitations", "Invitations": "Invitations",
"Is enabled": "Está habilitado", "Is enabled": "Está habilitado",
@ -312,6 +327,10 @@
"Password - Tooltip": "Certifique-se de que a senha está correta", "Password - Tooltip": "Certifique-se de que a senha está correta",
"Password complexity options": "Password complexity options", "Password complexity options": "Password complexity options",
"Password complexity options - Tooltip": "Password complexity options - Tooltip", "Password complexity options - Tooltip": "Password complexity options - Tooltip",
"Password obf key": "Password obf key",
"Password obf key - Tooltip": "Password obf key - Tooltip",
"Password obfuscator": "Password obfuscator",
"Password obfuscator - Tooltip": "Password obfuscator - Tooltip",
"Password salt": "Salt de senha", "Password salt": "Salt de senha",
"Password salt - Tooltip": "Parâmetro aleatório usado para criptografia de senha", "Password salt - Tooltip": "Parâmetro aleatório usado para criptografia de senha",
"Password type": "Tipo de senha", "Password type": "Tipo de senha",
@ -559,7 +578,6 @@
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code", "Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",
"Verify Code": "Verify Code", "Verify Code": "Verify Code",
"Verify Password": "Verify Password", "Verify Password": "Verify Password",
"You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue", "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue": "You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue",
@ -574,6 +592,8 @@
"Secret Key": "Secret Key" "Secret Key": "Secret Key"
}, },
"model": { "model": {
"Advanced Editor": "Editor Avançado",
"Basic Editor": "Editor Básico",
"Edit Model": "Editar Modelo", "Edit Model": "Editar Modelo",
"Model text": "Texto do Modelo", "Model text": "Texto do Modelo",
"Model text - Tooltip": "Modelo de controle de acesso Casbin, incluindo modelos incorporados como ACL, RBAC, ABAC, RESTful, etc. Você também pode criar modelos personalizados. Para obter mais informações, visite o site do Casbin", "Model text - Tooltip": "Modelo de controle de acesso Casbin, incluindo modelos incorporados como ACL, RBAC, ABAC, RESTful, etc. Você também pode criar modelos personalizados. Para obter mais informações, visite o site do Casbin",
@ -592,6 +612,8 @@
"Modify rule": "Modificar regra", "Modify rule": "Modificar regra",
"New Organization": "Nova Organização", "New Organization": "Nova Organização",
"Optional": "Optional", "Optional": "Optional",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt", "Prompt": "Prompt",
"Required": "Required", "Required": "Required",
"Soft deletion": "Exclusão suave", "Soft deletion": "Exclusão suave",
@ -709,7 +731,6 @@
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Comprar", "Buy": "Comprar",
"Buy Product": "Comprar Produto", "Buy Product": "Comprar Produto",
"CNY": "CNY",
"Detail": "Detalhe", "Detail": "Detalhe",
"Detail - Tooltip": "Detalhes do produto", "Detail - Tooltip": "Detalhes do produto",
"Dummy": "Dummy", "Dummy": "Dummy",
@ -740,7 +761,6 @@
"Test buy page..": "Página de teste de compra...", "Test buy page..": "Página de teste de compra...",
"There is no payment channel for this product.": "Não há canal de pagamento disponível para este produto.", "There is no payment channel for this product.": "Não há canal de pagamento disponível para este produto.",
"This product is currently not in sale.": "Este produto não está disponível para venda no momento.", "This product is currently not in sale.": "Este produto não está disponível para venda no momento.",
"USD": "USD",
"WeChat Pay": "WeChat Pay" "WeChat Pay": "WeChat Pay"
}, },
"provider": { "provider": {
@ -893,6 +913,7 @@
"Signup HTML - Edit": "Editar HTML de inscrição", "Signup HTML - Edit": "Editar HTML de inscrição",
"Signup HTML - Tooltip": "HTML personalizado para substituir o estilo padrão da página de inscrição", "Signup HTML - Tooltip": "HTML personalizado para substituir o estilo padrão da página de inscrição",
"Signup group": "Signup group", "Signup group": "Signup group",
"Signup group - Tooltip": "Signup group - Tooltip",
"Silent": "Silencioso", "Silent": "Silencioso",
"Site key": "Chave do site", "Site key": "Chave do site",
"Site key - Tooltip": "Chave do site", "Site key - Tooltip": "Chave do site",
@ -1157,6 +1178,7 @@
"Keys": "Chaves", "Keys": "Chaves",
"Language": "Idioma", "Language": "Idioma",
"Language - Tooltip": "Idioma - Tooltip", "Language - Tooltip": "Idioma - Tooltip",
"Last change password time": "Last change password time",
"Link": "Link", "Link": "Link",
"Location": "Localização", "Location": "Localização",
"Location - Tooltip": "Cidade de residência", "Location - Tooltip": "Cidade de residência",

Some files were not shown because too many files have changed in this diff Show More