Compare commits

...

53 Commits

Author SHA1 Message Date
44ae76503e feat: add default user mapping in custom oauth2 provider (#2819) 2024-03-18 23:01:17 +08:00
ae1634a4d5 feat: fix user cannot logout issue about bug in GetSessionToken() 2024-03-18 02:11:39 +08:00
bdf9864f69 fix: add FaceIdSigninBegin() to verify user information before face login (#2815)
* feat: add FaceIdSigninBegin() to verify user information before face login

* Update face.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-18 00:04:12 +08:00
72839d6bf5 feat: fix TokenFormat error in get-account API 2024-03-17 23:03:50 +08:00
2c4b1093ed fix: Correct expiresIn calculation for WeChat Mini Program token. (#2814) 2024-03-17 22:20:21 +08:00
d1c55d5aa7 fix: improve error message in token_cas.go 2024-03-17 22:01:49 +08:00
c8aa35c9c6 feat: add token to the page for Chrome extension (#2804)
* feat: add token to the page for Chrome extension

* Update token_oauth.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-17 22:01:28 +08:00
6037f37b87 feat: add default token format for built-in app 2024-03-17 20:46:01 +08:00
1b478903d8 feat: fix login page error cannot show bug 2024-03-17 11:39:12 +08:00
4f5ac7a10b Fix Face IDs label 2024-03-17 09:56:24 +08:00
e81ba62234 Improve Face ID signin method UI 2024-03-17 09:56:23 +08:00
Ron
a19060c7cb fix: missing parameter type_token_hint in IntrospectToken() (#2812)
* fix: missing parameter type_token_hint in IntrospectToken(); fix key token type
2024-03-17 01:39:04 +08:00
96812f676b fix: "fs" module not found issue in face-api.js for browser usage (#2810) 2024-03-17 01:35:43 +08:00
04f0458b5c feat: improve handleCameraError() and camera call logic (#2809)
add i18n for face recognition
2024-03-16 22:52:57 +08:00
fd0bcd9a17 Improve getObject() for "/api/get-policies" 2024-03-16 21:42:00 +08:00
01a5958307 Improve error text in RequireAdmin() 2024-03-16 21:14:19 +08:00
be88b00278 feat: improve RequireAdmin() logic 2024-03-16 20:49:17 +08:00
1bd0245e7a Improve CheckVerificationCode() error message, add receiver to index 2024-03-16 18:16:29 +08:00
cc84bd37cf Add object field in RecordListPage 2024-03-16 16:57:04 +08:00
8302fcf805 Improve handleCameraError() 2024-03-16 09:55:55 +08:00
391a533ce1 feat: add "Face ID" login method (#2782)
Face Login via face-api.js
2024-03-16 09:04:00 +08:00
57431a59ad fix: Ensure /api/get-app-login Returns Captcha Provider for Applications Configured with Captcha (#2800)
In LoginPage.js, the line 92:const captchaProviderItems = this.getCaptchaProviderItems(this.props.application); 

captchaProviderItems have no Captcha Provider.
2024-03-15 19:56:12 +08:00
88a4736520 feat: fix GetDashboard() page 2024-03-15 19:52:19 +08:00
2cb6ff69ae fix: show selected organizations' statistics in dashboard page (#2805)
* fix: show selected organizations' statistics in dashboard page

* Update get-dashboard.go

* Update saml_idp.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-15 19:36:39 +08:00
e1e5943a3e fix: fix the issue of adding xmlns="" when generating XML (#2799)
* fix:solve the problem of adding xmlns="" when generating XML

* fix:remove fmt.Println

* Update saml_idp.go

---------

Co-authored-by: zhaoxianfei <zhaoxianfei@meiqia.cn>
Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-13 23:59:05 +08:00
3875896c1e feat: support custom header logo (#2801)
* feat: support custom header logo

* feat: add i18n

* feat: preview default logo when field is empty

* feat: improve logo setting and display logic

* feat: change logoLight to logo
2024-03-13 23:33:43 +08:00
7e2f265420 feat: improve organization select UI (#2798) 2024-03-12 19:39:53 +08:00
53ef179e9b Set Webhook.Url length to 200 2024-03-11 18:18:01 +08:00
376ef0ed14 feat: support custom Email content in /send-email API 2024-03-11 11:48:00 +08:00
ca183be336 Improve ManagedAccountTable UI 2024-03-11 00:13:34 +08:00
e5da57a005 feat: fix cert's ES options 2024-03-10 19:30:05 +08:00
e4e225db32 Use "ES512" value 2024-03-10 19:25:41 +08:00
a1add992ee Support legacy "RSA" value 2024-03-10 19:23:54 +08:00
2aac265ed4 Improve populateContent() 2024-03-10 18:58:53 +08:00
2dc755f529 fix: add more cert algorithms like ES256 and PS256 (#2793) 2024-03-10 18:39:41 +08:00
0dd474d5fc feat: fix public profile page shows blank page bug 2024-03-10 14:12:24 +08:00
6998451e97 fix: support roles and permissions in /userinfo API 2024-03-10 12:34:56 +08:00
9175e5b664 Fix bug in GetMaskedEmail() 2024-03-10 11:49:55 +08:00
dbc6b0dc45 feat: fix issue that forget password page fails to redirect back to signin page (#2792) 2024-03-10 09:55:44 +08:00
31b7000f6a fix: enable the only language for login page 2024-03-09 11:28:23 +08:00
d25eaa65cd feat: support custom page footer (#2790) 2024-03-08 23:11:03 +08:00
f5bcd00652 Add language to records page 2024-03-08 23:03:30 +08:00
0d5f49e40a fix: fix GetResources() bug for app users 2024-03-08 16:15:31 +08:00
3527e070a0 Fix my account page UI 2024-03-08 15:18:18 +08:00
0108b58db4 Return status 200 for unauthorized operation, revert commit: 2fd2d88d20 2024-03-08 15:11:25 +08:00
976b5766a5 feat: refactor out token_oauth.go 2024-03-08 15:03:28 +08:00
a92d20162a feat: show all resources for org admin 2024-03-08 15:03:03 +08:00
204b1c2b8c Fix resource page link error 2024-03-08 14:44:39 +08:00
49fb269170 Improve error handling for GetSamlResponse() 2024-03-08 02:17:50 +08:00
c532a5d54d Remove suspense fallback loading. 2024-03-07 23:21:25 +08:00
89df80baca feat: remove loading fallback in Suspense and use spin to display (#2780) 2024-03-06 20:30:54 +08:00
d988ac814c fix: fix account items display error (#2781) 2024-03-06 20:30:34 +08:00
e4b25055d5 Improve isAllowedInDemoMode() 2024-03-06 02:17:28 +08:00
106 changed files with 2764 additions and 905 deletions

View File

@ -98,6 +98,7 @@ p, *, *, GET, /api/get-all-objects, *, *
p, *, *, GET, /api/get-all-actions, *, *
p, *, *, GET, /api/get-all-roles, *, *
p, *, *, GET, /api/get-invitation-info, *, *
p, *, *, GET, /api/faceid-signin-begin, *, *
`
sa := stringadapter.NewAdapter(ruleText)
@ -161,6 +162,11 @@ func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath
return true
}
return false
} else if urlPath == "/api/upload-resource" {
if subOwner == "app" && subName == "app-casibase" {
return true
}
return false
} else {
return false
}

View File

@ -307,6 +307,7 @@ func (c *ApiController) Logout() {
}
c.ClearUserSession()
c.ClearTokenSession()
owner, username := util.GetOwnerAndNameFromId(user)
_, err := object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
if err != nil {
@ -353,6 +354,7 @@ func (c *ApiController) Logout() {
}
c.ClearUserSession()
c.ClearTokenSession()
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
owner, username := util.GetOwnerAndNameFromId(user)
@ -433,6 +435,17 @@ func (c *ApiController) GetAccount() {
return
}
accessToken := c.GetSessionToken()
if accessToken == "" {
accessToken, err = object.GetAccessTokenByUser(user, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionToken(accessToken)
}
u.AccessToken = accessToken
resp := Response{
Status: "ok",
Sub: user.Id,
@ -459,7 +472,12 @@ func (c *ApiController) GetUserinfo() {
scope, aud := c.GetSessionOidc()
host := c.Ctx.Request.Host
userInfo := object.GetUserInfo(user, scope, aud, host)
userInfo, err := object.GetUserInfo(user, scope, aud, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = userInfo
c.ServeJSON()

View File

@ -327,7 +327,38 @@ func (c *ApiController) Login() {
}
var user *object.User
if authForm.Password == "" {
if authForm.SigninMethod == "Face ID" {
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error(), nil)
return
} else if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
return
}
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
if !application.IsFaceIdEnabled() {
c.ResponseError(c.T("auth:The login method: login with face is not enabled for the application"))
return
}
if err := object.CheckFaceId(user, authForm.FaceId, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error(), nil)
return
}
} else if authForm.Password == "" {
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error(), nil)
return

View File

@ -122,6 +122,15 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionToken() string {
accessToken := c.GetSession("accessToken")
if accessToken == nil {
return ""
}
return accessToken.(string)
}
func (c *ApiController) GetSessionApplication() *object.Application {
clientId := c.GetSession("aud")
if clientId == nil {
@ -141,6 +150,10 @@ func (c *ApiController) ClearUserSession() {
c.SetSessionData(nil)
}
func (c *ApiController) ClearTokenSession() {
c.SetSessionToken("")
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&
@ -167,6 +180,10 @@ func (c *ApiController) SetSessionUsername(user string) {
c.SetSession("username", user)
}
func (c *ApiController) SetSessionToken(accessToken string) {
c.SetSession("accessToken", accessToken)
}
// GetSessionData ...
func (c *ApiController) GetSessionData() *SessionData {
session := c.GetSession("SessionData")

55
controllers/face.go Normal file
View File

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

View File

@ -85,6 +85,11 @@ func (c *ApiController) GetRecords() {
// @Success 200 {object} object.Record The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
_, ok := c.RequireAdmin()
if !ok {
return
}
body := string(c.Ctx.Input.RequestBody)
record := &casvisorsdk.Record{}

View File

@ -52,6 +52,15 @@ func (c *ApiController) GetResources() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
isOrgAdmin, ok := c.IsOrgAdmin()
if !ok {
return
}
if isOrgAdmin {
user = ""
}
if sortField == "Direct" {
provider, err := c.GetProviderFromContext("Storage")
if err != nil {

View File

@ -60,7 +60,6 @@ func (c *ApiController) SendEmail() {
}
var emailForm EmailForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
if err != nil {
c.ResponseError(err.Error())
@ -87,7 +86,7 @@ func (c *ApiController) SendEmail() {
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
err := object.DailSmtpServer(provider)
err = object.DailSmtpServer(provider)
if err != nil {
c.ResponseError(err.Error())
return
@ -112,20 +111,23 @@ func (c *ApiController) SendEmail() {
return
}
code := "123456"
content := emailForm.Content
if content == "" {
code := "123456"
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := strings.Replace(provider.Content, "%s", code, 1)
if !strings.HasPrefix(userId, "app/") {
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content = strings.Replace(provider.Content, "%s", code, 1)
if !strings.HasPrefix(userId, "app/") {
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user != nil {
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
if user != nil {
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
}
}
}

View File

@ -317,7 +317,8 @@ func (c *ApiController) IntrospectToken() {
return
}
token, err := object.GetTokenByTokenValue(tokenValue)
tokenTypeHint := c.Input().Get("token_type_hint")
token, err := object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
if err != nil {
c.ResponseTokenError(err.Error())
return

View File

@ -108,12 +108,12 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
c.ResponseError(err.Error())
return nil, false
}
if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return nil, false
}
return user, true
}
@ -127,9 +127,39 @@ func (c *ApiController) RequireAdmin() (string, bool) {
if user.Owner == "built-in" {
return "", true
}
if !user.IsAdmin {
c.ResponseError(c.T("general:this operation requires administrator to perform"))
return "", false
}
return user.Owner, true
}
func (c *ApiController) IsOrgAdmin() (bool, bool) {
userId, ok := c.RequireSignedIn()
if !ok {
return false, true
}
if strings.HasPrefix(userId, "app/") {
return true, true
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return false, false
}
if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return false, false
}
return user.IsAdmin, true
}
// IsMaskedEnabled ...
func (c *ApiController) IsMaskedEnabled() (bool, bool) {
isMaskEnabled := true

View File

@ -61,6 +61,8 @@ type AuthForm struct {
Plan string `json:"plan"`
Pricing string `json:"pricing"`
FaceId []float64 `json:"faceId"`
}
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {

2
go.mod
View File

@ -14,7 +14,7 @@ require (
github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.6.0
github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.0.3
github.com/casvisor/casvisor-go-sdk v1.3.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect

4
go.sum
View File

@ -1093,8 +1093,8 @@ github.com/casdoor/oss v1.6.0 h1:IOWrGLJ+VO82qS796eaRnzFPPA1Sn3cotYTi7O/VIlQ=
github.com/casdoor/oss v1.6.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
github.com/casvisor/casvisor-go-sdk v1.0.3 h1:TKJQWKnhtznEBhzLPEdNsp7nJK2GgdD8JsB0lFPMW7U=
github.com/casvisor/casvisor-go-sdk v1.0.3/go.mod h1:frnNtH5GA0wxzAQLyZxxfL0RSsSub9GQPi2Ybe86ocE=
github.com/casvisor/casvisor-go-sdk v1.3.0 h1:HVgm2g3lWpNX2wBNidzR743QY4O5kAjLUJ9tS2juO8g=
github.com/casvisor/casvisor-go-sdk v1.3.0/go.mod h1:frnNtH5GA0wxzAQLyZxxfL0RSsSub9GQPi2Ybe86ocE=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert",
"The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
@ -38,6 +39,8 @@
"Email cannot be empty": "E-Mail darf nicht leer sein",
"Email is invalid": "E-Mail ist ungültig",
"Empty username.": "Leerer Benutzername.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "Vorname darf nicht leer sein",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación",
"The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
@ -38,6 +39,8 @@
"Email cannot be empty": "El correo electrónico no puede estar vacío",
"Email is invalid": "El correo electrónico no es válido",
"Empty username.": "Nombre de usuario vacío.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "El nombre no puede estar en blanco",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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": "La méthode de connexion : connexion avec mot de passe n'est pas activée pour l'application",
"The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
@ -38,6 +39,8 @@
"Email cannot be empty": "L'e-mail ne peut pas être vide",
"Email is invalid": "L'adresse e-mail est invalide",
"Empty username.": "Nom d'utilisateur vide.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "Le prénom ne peut pas être laissé vide",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut",
"The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email tidak boleh kosong",
"Email is invalid": "Email tidak valid",
"Empty username.": "Nama pengguna kosong.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
@ -38,6 +39,8 @@
"Email cannot be empty": "メールが空白にできません",
"Email is invalid": "電子メールは無効です",
"Empty username.": "空のユーザー名。",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "ファーストネームは空白にできません",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
@ -38,6 +39,8 @@
"Email cannot be empty": "이메일은 비어 있을 수 없습니다",
"Email is invalid": "이메일이 유효하지 않습니다",
"Empty username.": "빈 사용자 이름.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "이름은 공백일 수 없습니다",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
@ -38,6 +39,8 @@
"Email cannot be empty": "Электронная почта не может быть пустой",
"Email is invalid": "Адрес электронной почты недействительный",
"Empty username.": "Пустое имя пользователя.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "Имя не может быть пустым",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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 provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 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 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": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng",
"The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng",
@ -38,6 +39,8 @@
"Email cannot be empty": "Email không thể để trống",
"Email is invalid": "Địa chỉ email không hợp lệ",
"Empty username.": "Tên đăng nhập trống.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"FirstName cannot be blank": "Tên không được để trống",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",

View File

@ -18,6 +18,7 @@
"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 email 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 organization: %s does not exist": "组织: %s 不存在",
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
@ -38,6 +39,8 @@
"Email cannot be empty": "邮箱不可为空",
"Email is invalid": "无效邮箱",
"Empty username.": "用户名不可为空",
"Face data does not exist, cannot log in": "未录入人脸数据,无法登录",
"Face data mismatch": "人脸不匹配",
"FirstName cannot be blank": "名不可以为空",
"Invitation code cannot be blank": "邀请码不能为空",
"Invitation code exhausted": "邀请码使用次数已耗尽",

View File

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

View File

@ -110,6 +110,11 @@
"name": "WebAuthn",
"displayName": "WebAuthn",
"rule": "None"
},
{
"name": "Face ID",
"displayName": "Face ID",
"rule": "None"
}
],
"signupItems": [
@ -179,8 +184,10 @@
"refresh_token"
],
"redirectUris": [
""
"http://localhost:9000/callback"
],
"tokenFormat": "JWT",
"tokenFields": [],
"expireInHours": 168,
"failedSigninLimit": 5,
"failedSigninFrozenTime": 15

View File

@ -105,6 +105,7 @@ type Application struct {
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
ThemeData *ThemeData `xorm:"json" json:"themeData"`
FooterHtml string `xorm:"mediumtext" json:"footerHtml"`
FormCss string `xorm:"text" json:"formCss"`
FormCssMobile string `xorm:"text" json:"formCssMobile"`
FormOffset int `json:"formOffset"`
@ -310,6 +311,9 @@ func extendApplicationWithSigninMethods(application *Application) (err error) {
signinMethod := &SigninMethod{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"}
application.SigninMethods = append(application.SigninMethods, signinMethod)
}
signinMethod := &SigninMethod{Name: "Face ID", DisplayName: "Face ID", Rule: "None"}
application.SigninMethods = append(application.SigninMethods, signinMethod)
}
if len(application.SigninMethods) == 0 {
@ -504,7 +508,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
providerItems := []*ProviderItem{}
for _, providerItem := range application.Providers {
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3") {
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha") {
providerItems = append(providerItems, providerItem)
}
}
@ -528,11 +532,12 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***"
application.OrganizationObj.InitScore = -1
application.OrganizationObj.EnableSoftDeletion = false
application.OrganizationObj.IsProfilePublic = false
if !isOrgUser {
application.OrganizationObj.MfaItems = nil
application.OrganizationObj.AccountItems = nil
if !application.OrganizationObj.IsProfilePublic {
application.OrganizationObj.AccountItems = nil
}
}
}
@ -755,6 +760,17 @@ func (application *Application) IsLdapEnabled() bool {
return false
}
func (application *Application) IsFaceIdEnabled() bool {
if len(application.SigninMethods) > 0 {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "Face ID" {
return true
}
}
}
return false
}
func IsOriginAllowed(origin string) (bool, error) {
applications, err := GetApplications("")
if err != nil {

View File

@ -205,16 +205,41 @@ func (p *Cert) GetId() string {
}
func (p *Cert) populateContent() error {
if p.Certificate == "" || p.PrivateKey == "" {
certificate, privateKey, err := generateRsaKeys(p.BitSize, p.ExpireInYears, p.Name, p.Owner)
if err != nil {
return err
}
p.Certificate = certificate
p.PrivateKey = privateKey
if p.Certificate != "" && p.PrivateKey != "" {
return nil
}
if len(p.CryptoAlgorithm) < 3 {
err := fmt.Errorf("populateContent() error, unsupported crypto algorithm: %s", p.CryptoAlgorithm)
return err
}
if p.CryptoAlgorithm == "RSA" {
p.CryptoAlgorithm = "RS256"
}
sigAlgorithm := p.CryptoAlgorithm[:2]
shaSize, err := util.ParseIntWithError(p.CryptoAlgorithm[2:])
if err != nil {
return err
}
var certificate, privateKey string
if sigAlgorithm == "RS" {
certificate, privateKey, err = generateRsaKeys(p.BitSize, shaSize, p.ExpireInYears, p.Name, p.Owner)
} else if sigAlgorithm == "ES" {
certificate, privateKey, err = generateEsKeys(shaSize, p.ExpireInYears, p.Name, p.Owner)
} else if sigAlgorithm == "PS" {
certificate, privateKey, err = generateRsaPssKeys(p.BitSize, shaSize, p.ExpireInYears, p.Name, p.Owner)
} else {
err = fmt.Errorf("populateContent() error, unsupported signature algorithm: %s", sigAlgorithm)
}
if err != nil {
return err
}
p.Certificate = certificate
p.PrivateKey = privateKey
return nil
}

View File

@ -28,6 +28,10 @@ type Dashboard struct {
}
func GetDashboard(owner string) (*Dashboard, error) {
if owner == "All" {
owner = ""
}
dashboard := &Dashboard{
OrganizationCounts: make([]int, 31),
UserCounts: make([]int, 31),
@ -36,14 +40,13 @@ func GetDashboard(owner string) (*Dashboard, error) {
SubscriptionCounts: make([]int, 31),
}
var wg sync.WaitGroup
organizations := []Organization{}
users := []User{}
providers := []Provider{}
applications := []Application{}
subscriptions := []Subscription{}
var wg sync.WaitGroup
wg.Add(5)
go func() {
defer wg.Done()

View File

@ -184,6 +184,7 @@ func initBuiltInApplication() {
{Name: "Password", DisplayName: "Password", Rule: "All"},
{Name: "Verification code", DisplayName: "Verification code", Rule: "All"},
{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"},
{Name: "Face ID", DisplayName: "Face ID", Rule: "None"},
},
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
@ -197,6 +198,7 @@ func initBuiltInApplication() {
},
Tags: []string{},
RedirectUris: []string{},
TokenFormat: "JWT",
TokenFields: []string{},
ExpireInHours: 168,
FormOffset: 2,

View File

@ -54,6 +54,8 @@ type Organization struct {
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Logo string `xorm:"varchar(200)" json:"logo"`
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`

View File

@ -47,6 +47,12 @@ func NewRecord(ctx *context.Context) *casvisorsdk.Record {
object = string(ctx.Input.RequestBody)
}
language := ctx.Request.Header.Get("Accept-Language")
if len(language) > 2 {
language = language[0:2]
}
languageCode := conf.GetLanguage(language)
record := casvisorsdk.Record{
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
@ -55,7 +61,9 @@ func NewRecord(ctx *context.Context) *casvisorsdk.Record {
Method: ctx.Request.Method,
RequestUri: requestUri,
Action: action,
Language: languageCode,
Object: object,
Response: "",
IsTriggered: false,
}
return &record

View File

@ -179,8 +179,8 @@ type IdpSSODescriptor struct {
}
type NameIDFormat struct {
XMLName xml.Name
Value string `xml:",innerxml"`
// XMLName xml.Name
Value string `xml:",innerxml"`
}
type SingleSignOnService struct {
@ -190,7 +190,7 @@ type SingleSignOnService struct {
}
type Attribute struct {
XMLName xml.Name
// XMLName xml.Name
Name string `xml:"Name,attr"`
NameFormat string `xml:"NameFormat,attr"`
FriendlyName string `xml:"FriendlyName,attr"`
@ -273,7 +273,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
// base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", method, fmt.Errorf("err: Failed to decode SAML request , %s", err.Error())
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
}
// decompress
@ -281,7 +281,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
rdr := flate.NewReader(bytes.NewReader(defated))
for {
_, err := io.CopyN(&buffer, rdr, 1024)
_, err = io.CopyN(&buffer, rdr, 1024)
if err != nil {
if err == io.EOF {
break
@ -293,12 +293,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
var authnRequest saml.AuthNRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {
return "", "", method, 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())
}
// verify samlRequest
if isValid := application.IsRedirectUriValid(authnRequest.Issuer); !isValid {
return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer)
return "", "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer)
}
// get certificate string
@ -323,8 +323,13 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
}
_, originBackend := getOriginFromHost(host)
// build signedResponse
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer, authnRequest.ID, application.RedirectUris)
samlResponse, err := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer, authnRequest.ID, application.RedirectUris)
if err != nil {
return "", "", "", fmt.Errorf("err: NewSamlResponse() error, %s", err.Error())
}
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: certificate,
@ -336,18 +341,23 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
}
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
//if err != nil {
// signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
// if err != nil {
// return "", "", fmt.Errorf("err: %s", err.Error())
//}
// }
sig, err := ctx.ConstructSignature(samlResponse, true)
if err != nil {
return "", "", "", fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
}
samlResponse.InsertChildAt(1, sig)
doc := etree.NewDocument()
doc.SetRoot(samlResponse)
xmlBytes, err := doc.WriteToBytes()
if err != nil {
return "", "", method, fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
return "", "", "", fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
}
// compress
@ -355,16 +365,19 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
flated := bytes.NewBuffer(nil)
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
if err != nil {
return "", "", method, err
return "", "", "", err
}
_, err = writer.Write(xmlBytes)
if err != nil {
return "", "", "", err
}
err = writer.Close()
if err != nil {
return "", "", "", err
}
xmlBytes = flated.Bytes()
}
// base64 encode
@ -373,12 +386,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
}
// NewSamlResponse11 return a saml1.1 response(not 2.0)
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
func NewSamlResponse11(user *User, requestID string, host string) (*etree.Element, error) {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
}
// create samlresponse
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
samlResponse.CreateAttr("MajorVersion", "1")
samlResponse.CreateAttr("MinorVersion", "1")
@ -431,11 +444,15 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
data, _ := json.Marshal(user)
tmp := map[string]string{}
err := json.Unmarshal(data, &tmp)
data, err := json.Marshal(user)
if err != nil {
panic(err)
return nil, err
}
tmp := map[string]string{}
err = json.Unmarshal(data, &tmp)
if err != nil {
return nil, err
}
for k, v := range tmp {
@ -447,7 +464,7 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
}
}
return samlResponse
return samlResponse, nil
}
func GetSamlRedirectAddress(owner string, application string, relayState string, samlRequest string, host string) string {

View File

@ -16,33 +16,13 @@ package object
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"time"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
const (
hourSeconds = int(time.Hour / time.Second)
InvalidRequest = "invalid_request"
InvalidClient = "invalid_client"
InvalidGrant = "invalid_grant"
UnauthorizedClient = "unauthorized_client"
UnsupportedGrantType = "unsupported_grant_type"
InvalidScope = "invalid_scope"
EndpointError = "endpoint_error"
)
type Code struct {
Message string `xorm:"varchar(100)" json:"message"`
Code string `xorm:"varchar(100)" json:"code"`
}
type Token struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -65,35 +45,6 @@ type Token struct {
CodeExpireIn int64 `json:"codeExpireIn"`
}
type TokenWrapper struct {
AccessToken string `json:"access_token"`
IdToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
type IntrospectionResponse struct {
Active bool `json:"active"`
Scope string `json:"scope,omitempty"`
ClientId string `json:"client_id,omitempty"`
Username string `json:"username,omitempty"`
TokenType string `json:"token_type,omitempty"`
Exp int64 `json:"exp,omitempty"`
Iat int64 `json:"iat,omitempty"`
Nbf int64 `json:"nbf,omitempty"`
Sub string `json:"sub,omitempty"`
Aud []string `json:"aud,omitempty"`
Iss string `json:"iss,omitempty"`
Jti string `json:"jti,omitempty"`
}
func GetTokenCount(owner, organization, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Token{Organization: organization})
@ -186,21 +137,24 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
return &token, nil
}
func GetTokenByTokenValue(tokenValue string) (*Token, error) {
token, err := GetTokenByAccessToken(tokenValue)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
}
token, err = GetTokenByRefreshToken(tokenValue)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
switch tokenTypeHint {
case "access_token":
token, err := GetTokenByAccessToken(tokenValue)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
}
case "refresh_token":
token, err := GetTokenByRefreshToken(tokenValue)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
}
}
return nil, nil
@ -279,659 +233,3 @@ func DeleteToken(token *Token) (bool, error) {
return affected != 0, nil
}
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
token, err := GetTokenByAccessToken(accessToken)
if err != nil {
return false, nil, nil, err
}
if token == nil {
return false, nil, nil, nil
}
token.ExpiresIn = 0
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
if err != nil {
return false, nil, nil, err
}
application, err := getApplication(token.Owner, token.Application)
if err != nil {
return false, nil, nil, err
}
return affected != 0, application, token, nil
}
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application, error) {
if responseType != "code" && responseType != "token" && responseType != "id_token" {
return fmt.Sprintf(i18n.Translate(lang, "token:Grant_type: %s is not supported in this application"), responseType), nil, nil
}
application, err := GetApplicationByClientId(clientId)
if err != nil {
return "", nil, err
}
if application == nil {
return i18n.Translate(lang, "token:Invalid client_id"), nil, nil
}
if !application.IsRedirectUriValid(redirectUri) {
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application, nil
}
// Mask application for /api/get-app-login
application.ClientSecret = ""
return "", application, nil
}
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
user, err := GetUser(userId)
if err != nil {
return nil, err
}
if user == nil {
return &Code{
Message: fmt.Sprintf("general:The user: %s doesn't exist", userId),
Code: "",
}, nil
}
if user.IsForbidden {
return &Code{
Message: "error: the user is forbidden to sign in, please contact the administrator",
Code: "",
}, nil
}
msg, application, err := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, lang)
if err != nil {
return nil, err
}
if msg != "" {
return &Code{
Message: msg,
Code: "",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil {
return nil, err
}
if challenge == "null" {
challenge = ""
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeChallenge: challenge,
CodeIsUsed: false,
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
}
_, err = AddToken(token)
if err != nil {
return nil, err
}
return &Code{
Message: "",
Code: token.Code,
}, nil
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string) (interface{}, error) {
application, err := GetApplicationByClientId(clientId)
if err != nil {
return nil, err
}
if application == nil {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_id is invalid",
}, nil
}
// Check if grantType is allowed in the current application
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
return &TokenError{
Error: UnsupportedGrantType,
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
}, nil
}
var token *Token
var tokenError *TokenError
switch grantType {
case "authorization_code": // Authorization Code Grant
token, tokenError, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
case "password": // Resource Owner Password Credentials Grant
token, tokenError, err = GetPasswordToken(application, username, password, scope, host)
case "client_credentials": // Client Credentials Grant
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
case "refresh_token":
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil {
return nil, err
}
return refreshToken2, nil
}
if err != nil {
return nil, err
}
if tag == "wechat_miniprogram" {
// Wechat Mini Program
token, tokenError, err = GetWechatMiniProgramToken(application, code, host, username, avatar, lang)
if err != nil {
return nil, err
}
}
if tokenError != nil {
return tokenError, nil
}
token.CodeIsUsed = true
go updateUsedByCode(token)
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
IdToken: token.AccessToken,
RefreshToken: token.RefreshToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
}
return tokenWrapper, nil
}
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
// check parameters
if grantType != "refresh_token" {
return &TokenError{
Error: UnsupportedGrantType,
ErrorDescription: "grant_type should be refresh_token",
}, nil
}
application, err := GetApplicationByClientId(clientId)
if err != nil {
return nil, err
}
if application == nil {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_id is invalid",
}, nil
}
if clientSecret != "" && application.ClientSecret != clientSecret {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
// check whether the refresh token is valid, and has not expired.
token, err := GetTokenByRefreshToken(refreshToken)
if err != nil || token == nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: "refresh token is invalid, expired or revoked",
}, nil
}
cert, err := getCertByApplication(application)
if err != nil {
return nil, err
}
if cert == nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
}, nil
}
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
// generate a new token
user, err := getUser(application.Organization, token.User)
if err != nil {
return nil, err
}
if user.IsForbidden {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
newToken := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: newAccessToken,
RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
}
_, err = AddToken(newToken)
if err != nil {
return nil, err
}
_, err = DeleteToken(token)
if err != nil {
return nil, err
}
tokenWrapper := &TokenWrapper{
AccessToken: newToken.AccessToken,
IdToken: newToken.AccessToken,
RefreshToken: newToken.RefreshToken,
TokenType: newToken.TokenType,
ExpiresIn: newToken.ExpiresIn,
Scope: newToken.Scope,
}
return tokenWrapper, nil
}
// PkceChallenge: base64-URL-encoded SHA256 hash of verifier, per rfc 7636
func pkceChallenge(verifier string) string {
sum := sha256.Sum256([]byte(verifier))
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
return challenge
}
// IsGrantTypeValid
// Check if grantType is allowed in the current application
// authorization_code is allowed by default
func IsGrantTypeValid(method string, grantTypes []string) bool {
if method == "authorization_code" {
return true
}
for _, m := range grantTypes {
if m == method {
return true
}
}
return false
}
// GetAuthorizationCodeToken
// Authorization code flow
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError, error) {
if code == "" {
return nil, &TokenError{
Error: InvalidRequest,
ErrorDescription: "authorization code should not be empty",
}, nil
}
token, err := getTokenByCode(code)
if err != nil {
return nil, nil, err
}
if token == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "authorization code is invalid",
}, nil
}
if token.CodeIsUsed {
// anti replay attacks
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "authorization code has been used",
}, nil
}
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "verifier is invalid",
}, nil
}
if application.ClientSecret != clientSecret {
// when using PKCE, the Client Secret can be empty,
// but if it is provided, it must be accurate.
if token.CodeChallenge == "" {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
} else {
if clientSecret != "" {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
}
}
if application.Name != token.Application {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the token is for wrong application (client_id)",
}, nil
}
if time.Now().Unix() > token.CodeExpireIn {
// code must be used within 5 minutes
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "authorization code has expired",
}, nil
}
return token, nil, nil
}
// GetPasswordToken
// Resource Owner Password Credentials flow
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError, error) {
user, err := GetUserByFields(application.Organization, username)
if err != nil {
return nil, nil, err
}
if user == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user does not exist",
}, nil
}
if user.Ldap != "" {
err = checkLdapUserPassword(user, password, "en")
} else {
err = CheckPassword(user, password, "en")
}
if err != nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
}, nil
}
if user.IsForbidden {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}
// GetClientCredentialsToken
// Client Credentials flow
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError, error) {
if application.ClientSecret != clientSecret {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
nullUser := &User{
Owner: application.Owner,
Id: application.GetId(),
Name: application.Name,
Type: "application",
}
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: application.Organization,
User: nullUser.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}
// GetTokenByUser
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
err := ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil {
return nil, err
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, err
}
return token, nil
}
// GetWechatMiniProgramToken
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string, lang string) (*Token, *TokenError, error) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "the application does not support wechat mini program",
}, nil
}
provider, err := GetProvider(util.GetId("admin", mpProvider.Name))
if err != nil {
return nil, nil, err
}
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code)
if err != nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
}, nil
}
openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" {
return nil, &TokenError{
Error: InvalidRequest,
ErrorDescription: "the wechat mini program session is invalid",
}, nil
}
user, err := getUserByWechatId(application.Organization, openId, unionId)
if err != nil {
return nil, nil, err
}
if user == nil {
if !application.EnableSignUp {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the application does not allow to sign up new account",
}, nil
}
// Add new user
var name string
if CheckUsername(username, lang) == "" {
name = username
} else {
name = fmt.Sprintf("wechat-%s", openId)
}
user = &User{
Owner: application.Organization,
Id: util.GenerateId(),
Name: name,
Avatar: avatar,
SignupApplication: application.Name,
WeChat: openId,
Type: "normal-user",
CreatedTime: util.GetCurrentTime(),
IsAdmin: false,
IsForbidden: false,
IsDeleted: false,
Properties: map[string]string{
UserPropertiesWechatOpenId: openId,
UserPropertiesWechatUnionId: unionId,
},
}
_, err = AddUser(user)
if err != nil {
return nil, nil, err
}
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: session.SessionKey, // a trick, because miniprogram does not use the code, so use the code field to save the session_key
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: "",
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}

View File

@ -256,12 +256,12 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
ticket := request.AssertionArtifact.InnerXML
if ticket == "" {
return "", "", fmt.Errorf("samlp:AssertionArtifact field not found")
return "", "", fmt.Errorf("request.AssertionArtifact.InnerXML error, AssertionArtifact field not found")
}
ok, _, service, userId := GetCasTokenByTicket(ticket)
if !ok {
return "", "", fmt.Errorf("ticket %s found", ticket)
return "", "", fmt.Errorf("the CAS token for ticket %s is not found", ticket)
}
user, err := GetUser(userId)
@ -270,7 +270,7 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
}
if user == nil {
return "", "", fmt.Errorf("user %s found", userId)
return "", "", fmt.Errorf("the user %s is not found", userId)
}
application, err := GetApplicationByUser(user)
@ -279,10 +279,13 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
}
if application == nil {
return "", "", fmt.Errorf("application for user %s found", userId)
return "", "", fmt.Errorf("the application for user %s is not found", userId)
}
samlResponse := NewSamlResponse11(user, request.RequestID, host)
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
if err != nil {
return "", "", err
}
cert, err := getCertByApplication(application)
if err != nil {

View File

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

View File

@ -15,16 +15,19 @@
package object
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
)
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string, error) {
func generateRsaKeys(bitSize int, shaSize int, expireInYears int, commonName string, organization string) (string, string, error) {
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
@ -55,6 +58,132 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
BasicConstraintsValid: true,
}
switch shaSize {
case 256:
tml.SignatureAlgorithm = x509.SHA256WithRSA
case 384:
tml.SignatureAlgorithm = x509.SHA384WithRSA
case 512:
tml.SignatureAlgorithm = x509.SHA512WithRSA
default:
return "", "", fmt.Errorf("generateRsaKeys() error, unsupported SHA size: %d", shaSize)
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
return "", "", err
}
// Generate a pem block with the certificate
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
})
return string(certPem), string(privateKeyPem), nil
}
func generateEsKeys(shaSize int, expireInYears int, commonName string, organization string) (string, string, error) {
var curve elliptic.Curve
switch shaSize {
case 256:
curve = elliptic.P256()
case 384:
curve = elliptic.P384()
case 512:
curve = elliptic.P521() // ES512(P521,SHA512)
default:
return "", "", fmt.Errorf("generateEsKeys() error, unsupported SHA size: %d", shaSize)
}
// Generate ECDSA key pair.
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return "", "", err
}
// Encode private key to PEM format.
privateKeyBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return "", "", err
}
privateKeyPem := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: privateKeyBytes,
})
// Generate certificate template.
template := x509.Certificate{
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{organization},
},
BasicConstraintsValid: true,
}
// Generate certificate.
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return "", "", err
}
// Encode certificate to PEM format.
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
return string(certPem), string(privateKeyPem), nil
}
func generateRsaPssKeys(bitSize int, shaSize int, expireInYears int, commonName string, organization string) (string, string, error) {
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
return "", "", err
}
// Encode private key to PKCS#8 ASN.1 PEM.
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return "", "", err
}
privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PSS PRIVATE KEY",
Bytes: privateKeyBytes,
},
)
tml := x509.Certificate{
// you can add any attr that you need
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
// you have to generate a different serial number each execution
SerialNumber: big.NewInt(123456),
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{organization},
},
BasicConstraintsValid: true,
}
// Set the signature algorithm based on the hash function
switch shaSize {
case 256:
tml.SignatureAlgorithm = x509.SHA256WithRSAPSS
case 384:
tml.SignatureAlgorithm = x509.SHA384WithRSAPSS
case 512:
tml.SignatureAlgorithm = x509.SHA512WithRSAPSS
default:
return "", "", fmt.Errorf("generateRsaPssKeys() error, unsupported SHA size: %d", shaSize)
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
return "", "", err

View File

@ -23,7 +23,35 @@ import (
func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey, err := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
certificate, privateKey, err := generateRsaKeys(4096, 512, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}
// Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
}
func TestGenerateEsKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey, err := generateEsKeys(256, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}
// Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
}
func TestGenerateRsaPssKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey, err := generateRsaPssKeys(4096, 256, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}

745
object/token_oauth.go Normal file
View File

@ -0,0 +1,745 @@
// 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 (
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
const (
hourSeconds = int(time.Hour / time.Second)
InvalidRequest = "invalid_request"
InvalidClient = "invalid_client"
InvalidGrant = "invalid_grant"
UnauthorizedClient = "unauthorized_client"
UnsupportedGrantType = "unsupported_grant_type"
InvalidScope = "invalid_scope"
EndpointError = "endpoint_error"
)
type Code struct {
Message string `xorm:"varchar(100)" json:"message"`
Code string `xorm:"varchar(100)" json:"code"`
}
type TokenWrapper struct {
AccessToken string `json:"access_token"`
IdToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
type IntrospectionResponse struct {
Active bool `json:"active"`
Scope string `json:"scope,omitempty"`
ClientId string `json:"client_id,omitempty"`
Username string `json:"username,omitempty"`
TokenType string `json:"token_type,omitempty"`
Exp int64 `json:"exp,omitempty"`
Iat int64 `json:"iat,omitempty"`
Nbf int64 `json:"nbf,omitempty"`
Sub string `json:"sub,omitempty"`
Aud []string `json:"aud,omitempty"`
Iss string `json:"iss,omitempty"`
Jti string `json:"jti,omitempty"`
}
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
token, err := GetTokenByAccessToken(accessToken)
if err != nil {
return false, nil, nil, err
}
if token == nil {
return false, nil, nil, nil
}
token.ExpiresIn = 0
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
if err != nil {
return false, nil, nil, err
}
application, err := getApplication(token.Owner, token.Application)
if err != nil {
return false, nil, nil, err
}
return affected != 0, application, token, nil
}
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application, error) {
if responseType != "code" && responseType != "token" && responseType != "id_token" {
return fmt.Sprintf(i18n.Translate(lang, "token:Grant_type: %s is not supported in this application"), responseType), nil, nil
}
application, err := GetApplicationByClientId(clientId)
if err != nil {
return "", nil, err
}
if application == nil {
return i18n.Translate(lang, "token:Invalid client_id"), nil, nil
}
if !application.IsRedirectUriValid(redirectUri) {
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application, nil
}
// Mask application for /api/get-app-login
application.ClientSecret = ""
return "", application, nil
}
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
user, err := GetUser(userId)
if err != nil {
return nil, err
}
if user == nil {
return &Code{
Message: fmt.Sprintf("general:The user: %s doesn't exist", userId),
Code: "",
}, nil
}
if user.IsForbidden {
return &Code{
Message: "error: the user is forbidden to sign in, please contact the administrator",
Code: "",
}, nil
}
msg, application, err := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, lang)
if err != nil {
return nil, err
}
if msg != "" {
return &Code{
Message: msg,
Code: "",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil {
return nil, err
}
if challenge == "null" {
challenge = ""
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeChallenge: challenge,
CodeIsUsed: false,
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
}
_, err = AddToken(token)
if err != nil {
return nil, err
}
return &Code{
Message: "",
Code: token.Code,
}, nil
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string) (interface{}, error) {
application, err := GetApplicationByClientId(clientId)
if err != nil {
return nil, err
}
if application == nil {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_id is invalid",
}, nil
}
// Check if grantType is allowed in the current application
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
return &TokenError{
Error: UnsupportedGrantType,
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
}, nil
}
var token *Token
var tokenError *TokenError
switch grantType {
case "authorization_code": // Authorization Code Grant
token, tokenError, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
case "password": // Resource Owner Password Credentials Grant
token, tokenError, err = GetPasswordToken(application, username, password, scope, host)
case "client_credentials": // Client Credentials Grant
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
case "refresh_token":
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil {
return nil, err
}
return refreshToken2, nil
}
if err != nil {
return nil, err
}
if tag == "wechat_miniprogram" {
// Wechat Mini Program
token, tokenError, err = GetWechatMiniProgramToken(application, code, host, username, avatar, lang)
if err != nil {
return nil, err
}
}
if tokenError != nil {
return tokenError, nil
}
token.CodeIsUsed = true
go updateUsedByCode(token)
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
IdToken: token.AccessToken,
RefreshToken: token.RefreshToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
}
return tokenWrapper, nil
}
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
// check parameters
if grantType != "refresh_token" {
return &TokenError{
Error: UnsupportedGrantType,
ErrorDescription: "grant_type should be refresh_token",
}, nil
}
application, err := GetApplicationByClientId(clientId)
if err != nil {
return nil, err
}
if application == nil {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_id is invalid",
}, nil
}
if clientSecret != "" && application.ClientSecret != clientSecret {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
// check whether the refresh token is valid, and has not expired.
token, err := GetTokenByRefreshToken(refreshToken)
if err != nil || token == nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: "refresh token is invalid, expired or revoked",
}, nil
}
cert, err := getCertByApplication(application)
if err != nil {
return nil, err
}
if cert == nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
}, nil
}
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
// generate a new token
user, err := getUser(application.Organization, token.User)
if err != nil {
return nil, err
}
if user.IsForbidden {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
newToken := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: newAccessToken,
RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
}
_, err = AddToken(newToken)
if err != nil {
return nil, err
}
_, err = DeleteToken(token)
if err != nil {
return nil, err
}
tokenWrapper := &TokenWrapper{
AccessToken: newToken.AccessToken,
IdToken: newToken.AccessToken,
RefreshToken: newToken.RefreshToken,
TokenType: newToken.TokenType,
ExpiresIn: newToken.ExpiresIn,
Scope: newToken.Scope,
}
return tokenWrapper, nil
}
// PkceChallenge: base64-URL-encoded SHA256 hash of verifier, per rfc 7636
func pkceChallenge(verifier string) string {
sum := sha256.Sum256([]byte(verifier))
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
return challenge
}
// IsGrantTypeValid
// Check if grantType is allowed in the current application
// authorization_code is allowed by default
func IsGrantTypeValid(method string, grantTypes []string) bool {
if method == "authorization_code" {
return true
}
for _, m := range grantTypes {
if m == method {
return true
}
}
return false
}
// GetAuthorizationCodeToken
// Authorization code flow
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError, error) {
if code == "" {
return nil, &TokenError{
Error: InvalidRequest,
ErrorDescription: "authorization code should not be empty",
}, nil
}
token, err := getTokenByCode(code)
if err != nil {
return nil, nil, err
}
if token == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "authorization code is invalid",
}, nil
}
if token.CodeIsUsed {
// anti replay attacks
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "authorization code has been used",
}, nil
}
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "verifier is invalid",
}, nil
}
if application.ClientSecret != clientSecret {
// when using PKCE, the Client Secret can be empty,
// but if it is provided, it must be accurate.
if token.CodeChallenge == "" {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
} else {
if clientSecret != "" {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
}
}
if application.Name != token.Application {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the token is for wrong application (client_id)",
}, nil
}
if time.Now().Unix() > token.CodeExpireIn {
// code must be used within 5 minutes
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "authorization code has expired",
}, nil
}
return token, nil, nil
}
// GetPasswordToken
// Resource Owner Password Credentials flow
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError, error) {
user, err := GetUserByFields(application.Organization, username)
if err != nil {
return nil, nil, err
}
if user == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user does not exist",
}, nil
}
if user.Ldap != "" {
err = checkLdapUserPassword(user, password, "en")
} else {
err = CheckPassword(user, password, "en")
}
if err != nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
}, nil
}
if user.IsForbidden {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}
// GetClientCredentialsToken
// Client Credentials flow
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError, error) {
if application.ClientSecret != clientSecret {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
nullUser := &User{
Owner: application.Owner,
Id: application.GetId(),
Name: application.Name,
Type: "application",
}
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: application.Organization,
User: nullUser.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}
// GetTokenByUser
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
err := ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil {
return nil, err
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, err
}
return token, nil
}
// GetWechatMiniProgramToken
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string, lang string) (*Token, *TokenError, error) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, &TokenError{
Error: InvalidClient,
ErrorDescription: "the application does not support wechat mini program",
}, nil
}
provider, err := GetProvider(util.GetId("admin", mpProvider.Name))
if err != nil {
return nil, nil, err
}
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code)
if err != nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
}, nil
}
openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" {
return nil, &TokenError{
Error: InvalidRequest,
ErrorDescription: "the wechat mini program session is invalid",
}, nil
}
user, err := getUserByWechatId(application.Organization, openId, unionId)
if err != nil {
return nil, nil, err
}
if user == nil {
if !application.EnableSignUp {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the application does not allow to sign up new account",
}, nil
}
// Add new user
var name string
if CheckUsername(username, lang) == "" {
name = username
} else {
name = fmt.Sprintf("wechat-%s", openId)
}
user = &User{
Owner: application.Organization,
Id: util.GenerateId(),
Name: name,
Avatar: avatar,
SignupApplication: application.Name,
WeChat: openId,
Type: "normal-user",
CreatedTime: util.GetCurrentTime(),
IsAdmin: false,
IsForbidden: false,
IsDeleted: false,
Properties: map[string]string{
UserPropertiesWechatOpenId: openId,
UserPropertiesWechatUnionId: unionId,
},
}
_, err = AddUser(user)
if err != nil {
return nil, nil, err
}
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}, nil
}
token := &Token{
Owner: application.Owner,
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: session.SessionKey, // a trick, because miniprogram does not use the code, so use the code field to save the session_key
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: "",
TokenType: "Bearer",
CodeIsUsed: true,
}
_, err = AddToken(token)
if err != nil {
return nil, nil, err
}
return token, nil, nil
}
func GetAccessTokenByUser(user *User, host string) (string, error) {
application, err := GetApplicationByUser(user)
if err != nil {
return "", err
}
if application == nil {
return "", fmt.Errorf("the application for user %s is not found", user.Id)
}
token, err := GetTokenByUser(application, user, "profile", "", host)
if err != nil {
return "", err
}
return token.AccessToken, nil
}

View File

@ -98,6 +98,7 @@ type User struct {
PreHash string `xorm:"varchar(100)" json:"preHash"`
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
@ -190,6 +191,7 @@ type User struct {
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
Invitation string `xorm:"varchar(100) index" json:"invitation"`
InvitationCode string `xorm:"varchar(100) index" json:"invitationCode"`
FaceIds []*FaceId `json:"faceIds"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
@ -216,6 +218,8 @@ type Userinfo struct {
Address string `json:"address,omitempty"`
Phone string `json:"phone,omitempty"`
Groups []string `json:"groups,omitempty"`
Roles []string `json:"roles,omitempty"`
Permissions []string `json:"permissions,omitempty"`
}
type ManagedAccount struct {
@ -225,6 +229,11 @@ type ManagedAccount struct {
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
}
type FaceId struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
FaceIdData []float64 `json:"faceIdData"`
}
func GetUserFieldStringValue(user *User, fieldName string) (bool, string, error) {
val := reflect.ValueOf(*user)
fieldValue := val.FieldByName(fieldName)
@ -665,7 +674,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = []string{
"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",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
"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",
@ -914,7 +923,7 @@ func DeleteUser(user *User) (bool, error) {
return affected != 0, nil
}
func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
_, originBackend := getOriginFromHost(host)
resp := Userinfo{
@ -922,24 +931,44 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
Iss: originBackend,
Aud: aud,
}
if strings.Contains(scope, "profile") {
resp.Name = user.Name
resp.DisplayName = user.DisplayName
resp.Avatar = user.Avatar
resp.Groups = user.Groups
err := ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
resp.Roles = []string{}
for _, role := range user.Roles {
resp.Roles = append(resp.Roles, role.Name)
}
resp.Permissions = []string{}
for _, permission := range user.Permissions {
resp.Permissions = append(resp.Permissions, permission.Name)
}
}
if strings.Contains(scope, "email") {
resp.Email = user.Email
// resp.EmailVerified = user.EmailVerified
resp.EmailVerified = true
}
if strings.Contains(scope, "address") {
resp.Address = user.Location
}
if strings.Contains(scope, "phone") {
resp.Phone = user.Phone
}
return &resp
return &resp, nil
}
func LinkUserAccount(user *User, field string, value string) (bool, error) {

View File

@ -387,6 +387,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
itemsChanged = append(itemsChanged, item)
}
if newUser.FaceIds != nil {
item := GetAccountItemByName("Face ID", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsAdmin != newUser.IsAdmin {
item := GetAccountItemByName("Is admin", organization)
itemsChanged = append(itemsChanged, item)

View File

@ -17,6 +17,7 @@ package object
import (
"errors"
"fmt"
"math"
"math/rand"
"strings"
"time"
@ -53,7 +54,7 @@ type VerificationRecord struct {
Type string `xorm:"varchar(10)"`
User string `xorm:"varchar(100) notnull"`
Provider string `xorm:"varchar(100) notnull"`
Receiver string `xorm:"varchar(100) notnull"`
Receiver string `xorm:"varchar(100) index notnull"`
Code string `xorm:"varchar(10) notnull"`
Time int64 `xorm:"notnull"`
IsUsed bool
@ -183,7 +184,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
return nil, err
}
if record == nil {
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:Code has not been sent yet!")}, nil
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
}
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
@ -236,6 +237,28 @@ func CheckSigninCode(user *User, dest, code, lang string) error {
}
}
func CheckFaceId(user *User, faceId []float64, lang string) error {
if len(user.FaceIds) == 0 {
return fmt.Errorf(i18n.Translate(lang, "check:Face data does not exist, cannot log in"))
}
for _, userFaceId := range user.FaceIds {
if faceId == nil || len(userFaceId.FaceIdData) != len(faceId) {
continue
}
var sumOfSquares float64
for i := 0; i < len(userFaceId.FaceIdData); i++ {
diff := userFaceId.FaceIdData[i] - faceId[i]
sumOfSquares += diff * diff
}
if math.Sqrt(sumOfSquares) < 0.25 {
return nil
}
}
return fmt.Errorf(i18n.Translate(lang, "check:Face data mismatch"))
}
func GetVerifyType(username string) (verificationCodeType string) {
if strings.Contains(username, "@") {
return VerifyTypeEmail

View File

@ -33,7 +33,7 @@ type Webhook struct {
Organization string `xorm:"varchar(100) index" json:"organization"`
Url string `xorm:"varchar(100)" json:"url"`
Url string `xorm:"varchar(200)" json:"url"`
Method string `xorm:"varchar(100)" json:"method"`
ContentType string `xorm:"varchar(100)" json:"contentType"`
Headers []*Header `xorm:"mediumtext" json:"headers"`

View File

@ -66,10 +66,18 @@ func getObject(ctx *context.Context) (string, string) {
path := ctx.Request.URL.Path
if method == http.MethodGet {
if ctx.Request.URL.Path == "/api/get-policies" && ctx.Input.Query("id") == "/" {
adapterId := ctx.Input.Query("adapterId")
if adapterId != "" {
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
if ctx.Request.URL.Path == "/api/get-policies" {
if ctx.Input.Query("id") == "/" {
adapterId := ctx.Input.Query("adapterId")
if adapterId != "" {
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
}
} else {
// query == "?id=built-in/admin"
id := ctx.Input.Query("id")
if id != "" {
return util.GetOwnerAndNameFromIdNoCheck(id)
}
}
}

View File

@ -17,7 +17,6 @@ package routers
import (
"fmt"
"net"
"net/http"
"net/url"
"strings"
@ -36,7 +35,7 @@ type Response struct {
}
func responseError(ctx *context.Context, error string, data ...interface{}) {
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
// ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
resp := Response{Status: "error", Msg: error}
switch len(data) {

View File

@ -300,4 +300,6 @@ func initAPI() {
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
beego.Router("/scim/*", &controllers.RootController{}, "*:HandleScim")
beego.Router("/api/faceid-signin-begin", &controllers.ApiController{}, "GET:FaceIDSigninBegin")
}

View File

@ -264,6 +264,10 @@ func GetMaskedEmail(email string) string {
return ""
}
if !strings.Contains(email, "@") {
return maskString(email)
}
tokens := strings.Split(email, "@")
username := maskString(tokens[0])
domain := tokens[1]

View File

@ -93,6 +93,7 @@ module.exports = {
"buffer": false,
"crypto": false,
"os": false,
"fs": false,
},
}
},

View File

@ -29,6 +29,7 @@
"craco-less": "^2.0.0",
"echarts": "^5.4.3",
"ethers": "5.6.9",
"face-api.js": "^0.22.2",
"file-saver": "^2.0.5",
"i18n-iso-countries": "^7.0.0",
"i18next": "^19.8.9",

View File

@ -34,6 +34,7 @@ const ManagementPage = lazy(() => import("./ManagementPage"));
const {Footer, Content} = Layout;
import {setTwoToneColor} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend";
setTwoToneColor("rgb(87,52,211)");
@ -50,12 +51,14 @@ class App extends Component {
classes: props,
selectedMenuKey: 0,
account: undefined,
accessToken: undefined,
uri: null,
themeAlgorithm: storageThemeAlgorithm,
themeData: Conf.ThemeDefault,
logo: this.getLogo(storageThemeAlgorithm),
requiredEnableMfa: false,
isAiAssistantOpen: false,
application: undefined,
};
Setting.initServerUrl();
Auth.initAuthWithConfig({
@ -67,6 +70,7 @@ class App extends Component {
UNSAFE_componentWillMount() {
this.updateMenuKey();
this.getAccount();
this.getApplication();
}
componentDidUpdate(prevProps, prevState, snapshot) {
@ -150,11 +154,7 @@ class App extends Component {
}
getLogo(themes) {
if (themes.includes("dark")) {
return `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
} else {
return `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
}
return Setting.getLogo(themes);
}
setLanguage(account) {
@ -190,6 +190,24 @@ class App extends Component {
}
};
getApplication() {
const applicationName = localStorage.getItem("applicationName");
if (!applicationName) {
return;
}
ApplicationBackend.getApplication("admin", applicationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
application: res.data,
});
});
}
getAccount() {
const params = new URLSearchParams(this.props.location.search);
@ -211,9 +229,11 @@ class App extends Component {
AuthBackend.getAccount(query)
.then((res) => {
let account = null;
let accessToken = null;
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
accessToken = res.data.accessToken;
this.setLanguage(account);
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
@ -225,6 +245,7 @@ class App extends Component {
this.setState({
account: account,
accessToken: accessToken,
});
});
}
@ -239,17 +260,24 @@ class App extends Component {
return (
<React.Fragment>
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorAccessToken" value={this.state.accessToken} />}
<Footer id="footer" style={
{
textAlign: "center",
}
}>
{
Conf.CustomFooter !== null ? Conf.CustomFooter : (
this.state.application?.footerHtml && this.state.application.footerHtml !== "" ?
<React.Fragment>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
<div dangerouslySetInnerHTML={{__html: this.state.application.footerHtml}} />
</React.Fragment>
)
: (
Conf.CustomFooter !== null ? Conf.CustomFooter : (
<React.Fragment>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
</React.Fragment>
)
)
}
</Footer>
</React.Fragment>
@ -330,6 +358,11 @@ class App extends Component {
<EntryPage
account={this.state.account}
theme={this.state.themeData}
updateApplication={(application) => {
this.setState({
application: application,
});
}}
onLoginSuccess={(redirectUrl) => {
if (redirectUrl) {
localStorage.setItem("mfaRedirectUrl", redirectUrl);
@ -366,7 +399,7 @@ class App extends Component {
<FloatButton.BackTop />
<CustomGithubCorner />
{
<Suspense fallback={<div>loading</div>}>
<Suspense fallback={null}>
<Layout id="parent-area">
<ManagementPage
account={this.state.account}

View File

@ -887,6 +887,38 @@ class ApplicationEditPage extends React.Component {
</Popover>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Footer HTML"), i18next.t("application:Footer HTML - Tooltip"))} :
</Col>
<Col span={22} >
<Popover placement="right" content={
<div style={{width: "900px", height: "300px"}} >
<CodeMirror
value={this.state.application.footerHtml}
options={{mode: "htmlmixed", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {
this.updateApplicationField("footerHtml", value);
}}
/>
</div>
} title={i18next.t("application:Footer HTML - Edit")} trigger="click">
<Input value={this.state.application.footerHtml} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("footerHtml", e.target.value);
}} />
</Popover>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
</Col>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateApplicationField("footerHtml", Setting.getDefaultFooterContent())} >
{i18next.t("provider:Reset to Default HTML")}
</Button>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateApplicationField("footerHtml", Setting.getEmptyFooterContent())} >
{i18next.t("application:Reset to Empty")}
</Button>
</Row>
{
<React.Fragment>
<Row style={{marginTop: "20px"}} >
@ -1049,7 +1081,7 @@ class ApplicationEditPage extends React.Component {
submitApplicationEdit(exitAfterSave) {
const application = Setting.deepCopy(this.state.application);
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP"].includes(signinMethod.name));
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP", "Face ID"].includes(signinMethod.name));
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
.then((res) => {

View File

@ -50,6 +50,7 @@ class ApplicationListPage extends BaseListPage {
{name: "Password", displayName: "Password", rule: "All"},
{name: "Verification code", displayName: "Verification code", rule: "All"},
{name: "WebAuthn", displayName: "WebAuthn", rule: "None"},
{name: "Face ID", displayName: "Face ID", rule: "None"},
],
signupItems: [
{name: "ID", visible: false, required: true, rule: "Random"},

View File

@ -171,48 +171,54 @@ class CertEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
this.updateCertField("cryptoAlgorithm", value);
if (value === "RS256") {
this.updateCertField("bitSize", 2048);
} else if (value === "HS256" || value === "ES256") {
this.updateCertField("bitSize", 256);
} else if (value === "ES384") {
this.updateCertField("bitSize", 384);
} else if (value === "ES521") {
this.updateCertField("bitSize", 521);
} else {
if (value.startsWith("ES")) {
this.updateCertField("bitSize", 0);
} else {
if (this.state.cert.bitSize !== 1024 && this.state.cert.bitSize !== 2048 && this.state.cert.bitSize !== 4096) {
this.updateCertField("bitSize", 2048);
}
}
this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}>
{
[
{id: "RS256", name: "RS256 (RSA + SHA256)"},
{id: "HS256", name: "HS256 (HMAC + SHA256)"},
{id: "RS384", name: "RS384 (RSA + SHA384)"},
{id: "RS512", name: "RS512 (RSA + SHA512)"},
{id: "ES256", name: "ES256 (ECDSA using P-256 + SHA256)"},
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA256)"},
{id: "ES521", name: "ES521 (ECDSA using P-521 + SHA256)"},
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA384)"},
{id: "ES512", name: "ES512 (ECDSA using P-521 + SHA512)"},
{id: "PS256", name: "PS256 (RSASSA-PSS using SHA256 and MGF1 with SHA256)"},
{id: "PS384", name: "PS384 (RSASSA-PSS using SHA384 and MGF1 with SHA384)"},
{id: "PS512", name: "PS512 (RSASSA-PSS using SHA512 and MGF1 with SHA512)"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.bitSize} onChange={(value => {
this.updateCertField("bitSize", value);
this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}>
{
Setting.getCryptoAlgorithmOptions(this.state.cert.cryptoAlgorithm).map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
{
this.state.cert.cryptoAlgorithm.startsWith("ES") ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.bitSize} onChange={(value => {
this.updateCertField("bitSize", value);
this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}>
{
Setting.getCryptoAlgorithmOptions(this.state.cert.cryptoAlgorithm).map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} :

View File

@ -69,6 +69,11 @@ class EntryPage extends React.Component {
});
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
this.props.updataThemeData(themeData);
this.props.updateApplication(application);
if (application) {
localStorage.setItem("applicationName", application.name);
}
};
const onUpdatePricing = (pricing) => {

View File

@ -220,9 +220,29 @@ function ManagementPage(props) {
return [];
}
const textColor = props.themeAlgorithm.includes("dark") ? "white" : "black";
let textColor = "black";
const twoToneColor = props.themeData.colorPrimary;
let logo = props.account.organization.logo ? props.account.organization.logo : Setting.getLogo(props.themeAlgorithm);
if (props.themeAlgorithm.includes("dark")) {
if (props.account.organization.logoDark) {
logo = props.account.organization.logoDark;
}
textColor = "white";
}
!Setting.isMobile() ? res.push({
label:
<Link to="/">
<img className="logo" src={logo ?? props.logo} alt="logo" />
</Link>,
disabled: true,
style: {
padding: 0,
height: "auto",
},
}) : null;
res.push(Setting.getItem(<Link style={{color: textColor}} to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/">{i18next.t("general:Dashboard")}</Link>, "/"),
Setting.getItem(<Link to="/shortcuts">{i18next.t("general:Shortcuts")}</Link>, "/shortcuts"),
@ -400,11 +420,6 @@ function ManagementPage(props) {
<React.Fragment>
<EnableMfaNotification account={props.account} />
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}} >
{Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" style={{background: `url(${props.logo})`}} />
</Link>
)}
{props.requiredEnableMfa || (Setting.isMobile() ?
<React.Fragment>
<Drawer title={i18next.t("general:Close")} placement="left" visible={menuVisible} onClose={onClose}>
@ -426,7 +441,7 @@ function ManagementPage(props) {
items={getMenuItems()}
mode={"horizontal"}
selectedKeys={[props.selectedMenuKey]}
style={{position: "absolute", left: "145px", right: menuStyleRight, backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}}
style={{position: "absolute", left: 0, right: menuStyleRight, backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}}
/>
)}
{

View File

@ -56,6 +56,7 @@ class OrganizationEditPage extends React.Component {
this.props.history.push("/404");
return;
}
organization["enableDarkLogo"] = !!organization["logoDark"];
this.setState({
organization: organization,
@ -141,6 +142,78 @@ class OrganizationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Enable dark logo"), i18next.t("general:Enable dark logo - Tooltip"))} :
</Col>
<Col span={22} >
<Switch checked={this.state.organization.enableDarkLogo} onChange={e => {
this.updateOrganizationField("enableDarkLogo", e);
if (!e) {
this.updateOrganizationField("logoDark", "");
}
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined />} value={this.state.organization.logo} onChange={e => {
this.updateOrganizationField("logo", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23}>
<a target="_blank" rel="noreferrer" href={this.state.organization.logo}>
<img src={this.state.organization.logo ? this.state.organization.logo : Setting.getLogo([""])} alt={this.state.organization.logo} height={90} style={{background: "white", marginBottom: "20px"}} />
</a>
</Col>
</Row>
</Col>
</Row>
{
!this.state.organization.enableDarkLogo ? null : (<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Logo dark"), i18next.t("general:Logo dark - Tooltip"))} :
</Col>
<Col span={22}>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23}>
<Input prefix={<LinkOutlined />} value={this.state.organization.logoDark} onChange={e => {
this.updateOrganizationField("logoDark", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23}>
<a target="_blank" rel="noreferrer" href={this.state.organization.logoDark}>
<img
src={this.state.organization.logoDark ? this.state.organization.logoDark : Setting.getLogo(["dark"])}
alt={this.state.organization.logoDark} height={90}
style={{background: "#141414", marginBottom: "20px"}} />
</a>
</Col>
</Row>
</Col>
</Row>)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :

View File

@ -40,6 +40,14 @@ require("codemirror/mode/css/css");
const {Option} = Select;
const {TextArea} = Input;
const defaultUserMapping = {
id: "id",
username: "username",
displayName: "displayName",
email: "email",
avatarUrl: "avatarUrl",
};
class ProviderEditPage extends React.Component {
constructor(props) {
super(props);
@ -70,7 +78,7 @@ class ProviderEditPage extends React.Component {
if (res.status === "ok") {
const provider = res.data;
provider.userMapping = provider.userMapping || {};
provider.userMapping = provider.userMapping || defaultUserMapping;
this.setState({
provider: provider,
});
@ -141,8 +149,16 @@ class ProviderEditPage extends React.Component {
}
updateUserMappingField(key, value) {
const requiredKeys = ["id", "username", "displayName"];
const provider = this.state.provider;
if (value === "" && requiredKeys.includes(key)) {
Setting.showMessage("error", i18next.t("provider:This field is required"));
return;
}
provider.userMapping[key] = value;
this.setState({
provider: provider,
});

View File

@ -65,7 +65,7 @@ class RecordListPage extends BaseListPage {
title: i18next.t("general:Client IP"),
dataIndex: "clientIp",
key: "clientIp",
width: "150px",
width: "100px",
sorter: true,
...this.getColumnSearchProps("clientIp"),
render: (text, record, index) => {
@ -80,7 +80,7 @@ class RecordListPage extends BaseListPage {
title: i18next.t("general:Timestamp"),
dataIndex: "createdTime",
key: "createdTime",
width: "180px",
width: "150px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
@ -105,7 +105,7 @@ class RecordListPage extends BaseListPage {
title: i18next.t("general:User"),
dataIndex: "user",
key: "user",
width: "120px",
width: "100px",
sorter: true,
...this.getColumnSearchProps("user"),
render: (text, record, index) => {
@ -139,10 +139,26 @@ class RecordListPage extends BaseListPage {
title: i18next.t("general:Request URI"),
dataIndex: "requestUri",
key: "requestUri",
// width: '300px',
// width: "300px",
sorter: true,
...this.getColumnSearchProps("requestUri"),
},
{
title: i18next.t("user:Language"),
dataIndex: "language",
key: "language",
width: "90px",
sorter: true,
...this.getColumnSearchProps("language"),
},
{
title: i18next.t("record:Object"),
dataIndex: "object",
key: "object",
width: "90px",
sorter: true,
...this.getColumnSearchProps("object"),
},
{
title: i18next.t("general:Action"),
dataIndex: "action",

View File

@ -124,7 +124,7 @@ class ResourceListPage extends BaseListPage {
...this.getColumnSearchProps("application"),
render: (text, record, index) => {
return (
<Link to={`/applications/${record.organization}/${text}`}>
<Link to={`/applications/${record.owner}/${text}`}>
{text}
</Link>
);

View File

@ -89,6 +89,14 @@ export function getAlgorithmNames(themeData) {
return algorithms;
}
export function getLogo(themes) {
if (themes.includes("dark")) {
return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
} else {
return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
}
}
export const OtherProviderInfo = {
SMS: {
"Aliyun SMS": {
@ -1101,7 +1109,9 @@ export function getProviderTypeOptions(category) {
}
export function getCryptoAlgorithmOptions(cryptoAlgorithm) {
if (cryptoAlgorithm === "RS256") {
if (cryptoAlgorithm.startsWith("ES")) {
return [];
} else {
return (
[
{id: 1024, name: "1024"},
@ -1109,26 +1119,6 @@ export function getCryptoAlgorithmOptions(cryptoAlgorithm) {
{id: 4096, name: "4096"},
]
);
} else if (cryptoAlgorithm === "HS256" || cryptoAlgorithm === "ES256") {
return (
[
{id: 256, name: "256"},
]
);
} else if (cryptoAlgorithm === "ES384") {
return (
[
{id: 384, name: "384"},
]
);
} else if (cryptoAlgorithm === "ES521") {
return (
[
{id: 521, name: "521"},
]
);
} else {
return [];
}
}
@ -1174,6 +1164,10 @@ export function isLdapEnabled(application) {
return isSigninMethodEnabled(application, "LDAP");
}
export function isFaceIdEnabled(application) {
return isSigninMethodEnabled(application, "Face ID");
}
export function getLoginLink(application) {
let url;
if (application === null) {
@ -1467,6 +1461,19 @@ export function getUserCommonFields() {
"PreferredMfaType", "TotpSecret", "SignupApplication"];
}
export function getDefaultFooterContent() {
return "Powered by <a target=\"_blank\" href=\"https://casdoor.org\" rel=\"noreferrer\"><img style=\"padding-bottom: 3px\" height=\"20\" alt=\"Casdoor\" src=\"https://cdn.casbin.org/img/casdoor-logo_1185x256.png\"/></a>";
}
export function getEmptyFooterContent() {
return `<style>
#footer {
display: none;
}
<style>
`;
}
export function getDefaultHtmlEmailContent() {
return `<!DOCTYPE html>
<html lang="en">

View File

@ -40,6 +40,7 @@ import {DeleteMfa} from "./backend/MfaBackend";
import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
import * as MfaBackend from "./backend/MfaBackend";
import AccountAvatar from "./account/AccountAvatar";
import FaceIdTable from "./table/FaceIdTable";
const {Option} = Select;
@ -59,6 +60,7 @@ class UserEditPage extends React.Component {
loading: true,
returnUrl: null,
idCardInfo: ["ID card front", "ID card back", "ID card with person"],
openFaceRecognitionModal: false,
};
}
@ -201,6 +203,10 @@ class UserEditPage extends React.Component {
}
updateUserField(key, value) {
if (this.props.account === null) {
return;
}
value = this.parseUserField(key, value);
const user = this.state.user;
@ -251,22 +257,8 @@ class UserEditPage extends React.Component {
};
renderAccountItem(accountItem) {
if (!accountItem.visible) {
return null;
}
const isAdmin = Setting.isLocalAdminUser(this.props.account);
if (accountItem.viewRule === "Self") {
if (!this.isSelfOrAdmin()) {
return null;
}
} else if (accountItem.viewRule === "Admin") {
if (!isAdmin) {
return null;
}
}
let disabled = false;
if (accountItem.modifyRule === "Self") {
if (!this.isSelfOrAdmin()) {
@ -984,6 +976,21 @@ class UserEditPage extends React.Component {
</Col>
</Row>
);
} else if (accountItem.name === "Face ID") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Face IDs"), i18next.t("user:Face IDs"))} :
</Col>
<Col span={22} >
<FaceIdTable
title={i18next.t("user:Face IDs")}
table={this.state.user.faceIds}
onUpdateTable={(table) => {this.updateUserField("faceIds", table);}}
/>
</Col>
</Row>
);
}
}
@ -1003,7 +1010,11 @@ class UserEditPage extends React.Component {
<div style={{verticalAlign: "middle", marginBottom: 10}}>{`(${i18next.t("general:empty")})`}</div>
</Col>
}
<CropperDivModal disabled={disabled} tag={tag} setTitle={set} buttonText={`${title}...`} title={title} user={this.state.user} organization={this.getUserOrganization()} />
{
(this.props.account === null) ? null : (
<CropperDivModal disabled={disabled} tag={tag} setTitle={set} buttonText={`${title}...`} title={title} user={this.state.user} organization={this.getUserOrganization()} />
)
}
</Col>
);
}
@ -1013,7 +1024,7 @@ class UserEditPage extends React.Component {
<Card size="small" title={
(this.props.account === null) ? i18next.t("user:User Profile") : (
<div>
{this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}&nbsp;&nbsp;&nbsp;&nbsp;
{this.state.mode === "add" ? i18next.t("user:New User") : (this.isSelf() ? i18next.t("account:My Account") : i18next.t("user:Edit User"))}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
@ -1023,6 +1034,21 @@ class UserEditPage extends React.Component {
<Form>
{
this.getUserOrganization()?.accountItems?.map(accountItem => {
if (!accountItem.visible) {
return null;
}
const isAdmin = Setting.isLocalAdminUser(this.props.account);
if (accountItem.viewRule === "Self") {
if (!this.isSelfOrAdmin()) {
return null;
}
} else if (accountItem.viewRule === "Admin") {
if (!isAdmin) {
return null;
}
}
return (
<React.Fragment key={accountItem.name}>
<Form.Item name={accountItem.name}
@ -1093,7 +1119,9 @@ class UserEditPage extends React.Component {
}
}
} else {
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
if (location.pathname !== "/account") {
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
}
}
} else {
if (exitAfterSave) {

View File

@ -156,7 +156,7 @@ class ForgetPage extends React.Component {
if (res.status === "ok") {
const linkInStorage = sessionStorage.getItem("signinUrl");
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLinkSoft(linkInStorage);
Setting.goToLinkSoft(this, linkInStorage);
} else {
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import React, {Suspense, lazy} from "react";
import {Button, Checkbox, Col, Form, Input, Result, Spin, Tabs} from "antd";
import {ArrowLeftOutlined, LockOutlined, UserOutlined} from "@ant-design/icons";
import {withRouter} from "react-router-dom";
@ -36,6 +36,7 @@ import {CaptchaModal, CaptchaRule} from "../common/modal/CaptchaModal";
import RedirectForm from "../common/RedirectForm";
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
class LoginPage extends React.Component {
constructor(props) {
@ -52,6 +53,7 @@ class LoginPage extends React.Component {
validEmail: false,
enableCaptchaModal: CaptchaRule.Never,
openCaptchaModal: false,
openFaceRecognitionModal: false,
verifyCaptcha: undefined,
samlResponse: "",
relayState: "",
@ -214,6 +216,7 @@ class LoginPage extends React.Component {
}
case "WebAuthn": return "webAuthn";
case "LDAP": return "ldap";
case "Face ID": return "faceId";
}
}
@ -263,6 +266,8 @@ class LoginPage extends React.Component {
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();
@ -340,6 +345,31 @@ class LoginPage extends React.Component {
this.signInWithWebAuthn(username, values);
return;
}
if (this.state.loginMethod === "faceId") {
let username = this.state.username;
if (username === null || username === "") {
username = values["username"];
}
const application = this.getApplicationObj();
fetch(`${Setting.ServerUrl}/api/faceid-signin-begin?owner=${application.organization}&name=${username}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json())
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
openFaceRecognitionModal: true,
values: values,
});
});
return;
}
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
this.setState({
@ -521,6 +551,15 @@ class LoginPage extends React.Component {
</div>
);
} else if (signinItem.name === "Languages") {
const languages = application.organizationObj.languages;
if (languages.length <= 1) {
const language = (languages.length === 1) ? languages[0] : "en";
if (Setting.getLanguage() !== language) {
Setting.setLanguage(language);
}
return null;
}
return (
<div className="login-languages">
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
@ -645,9 +684,29 @@ class LoginPage extends React.Component {
>
{
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
i18next.t("login:Sign In")
this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
i18next.t("login:Sign In")
}
</Button>
{
this.state.loginMethod === "faceId" ?
<Suspense fallback={null}>
<FaceRecognitionModal
visible={this.state.openFaceRecognitionModal}
onOk={(faceId) => {
const values = this.state.values;
values["faceId"] = faceId;
this.login(values);
this.setState({openFaceRecognitionModal: false});
}}
onCancel={() => this.setState({openFaceRecognitionModal: false})}
/>
</Suspense>
:
<>
</>
}
{
this.renderCaptchaModal(application)
}
@ -712,7 +771,7 @@ class LoginPage extends React.Component {
);
}
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application);
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
if (showForm) {
let loginWidth = 320;
if (Setting.getLanguage() === "fr") {
@ -1020,12 +1079,18 @@ class LoginPage extends React.Component {
[generateItemKey("Verification code", "Phone only"), {label: i18next.t("login:Verification code"), key: "verificationCodePhone"}],
[generateItemKey("WebAuthn", "None"), {label: i18next.t("login:WebAuthn"), key: "webAuthn"}],
[generateItemKey("LDAP", "None"), {label: i18next.t("login:LDAP"), key: "ldap"}],
[generateItemKey("Face ID", "None"), {label: i18next.t("login:Face ID"), key: "faceId"}],
]);
application?.signinMethods?.forEach((signinMethod) => {
const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));
if (item) {
const label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;
let label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;
if (application?.signinMethods?.length >= 4 && label === "Verification code") {
label = "Code";
}
items.push({label: label, key: item.key});
}
});
@ -1108,7 +1173,7 @@ class LoginPage extends React.Component {
};
return (
<div style={{height: 300, width: 300}}>
<div style={{height: 300}}>
{renderChoiceBox()}
</div>
);
@ -1179,11 +1244,9 @@ class LoginPage extends React.Component {
</div>
<div className="login-form">
<div>
<div>
{
this.renderLoginPanel(application)
}
</div>
{
this.renderLoginPanel(application)
}
</div>
</div>
</div>

View File

@ -31,17 +31,32 @@ const Dashboard = (props) => {
return () => window.removeEventListener("storageTourChanged", handleTourChange);
}, []);
React.useEffect(() => {
window.addEventListener("storageOrganizationChanged", handleOrganizationChange);
return () => window.removeEventListener("storageOrganizationChanged", handleOrganizationChange);
}, [props.owner]);
React.useEffect(() => {
if (!Setting.isLocalAdminUser(props.account)) {
props.history.push("/apps");
}
}, [props.account]);
const getOrganizationName = () => {
let organization = localStorage.getItem("organization") === "All" ? "" : localStorage.getItem("organization");
if (!Setting.isAdminUser(props.account) && Setting.isLocalAdminUser(props.account)) {
organization = props.account.owner;
}
return organization;
};
React.useEffect(() => {
if (!Setting.isLocalAdminUser(props.account)) {
return;
}
DashboardBackend.getDashboard(props.account.owner).then((res) => {
const organization = getOrganizationName();
DashboardBackend.getDashboard(organization).then((res) => {
if (res.status === "ok") {
setDashboardData(res.data);
} else {
@ -54,6 +69,21 @@ const Dashboard = (props) => {
setIsTourVisible(TourConfig.getTourVisible());
};
const handleOrganizationChange = () => {
if (!Setting.isLocalAdminUser(props.account)) {
return;
}
const organization = getOrganizationName();
DashboardBackend.getDashboard(organization).then((res) => {
if (res.status === "ok") {
setDashboardData(res.data);
} else {
Setting.showMessage("error", res.msg);
}
});
};
const setIsTourToLocal = () => {
TourConfig.setIsTourVisible(false);
setIsTourVisible(false);

View File

@ -0,0 +1,220 @@
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as faceapi from "face-api.js";
import React, {useState} from "react";
import {Button, Modal, Progress, Spin, message} from "antd";
import i18next from "i18next";
const FaceRecognitionModal = (props) => {
const {visible, onOk, onCancel} = props;
const [modelsLoaded, setModelsLoaded] = React.useState(false);
const [isCameraCaptured, setIsCameraCaptured] = useState(false);
const videoRef = React.useRef();
const canvasRef = React.useRef();
const detection = React.useRef(null);
const mediaStreamRef = React.useRef(null);
const [percent, setPercent] = useState(0);
React.useEffect(() => {
const loadModels = async() => {
// const MODEL_URL = process.env.PUBLIC_URL + "/models";
// const MODEL_URL = "https://justadudewhohacks.github.io/face-api.js/models";
// const MODEL_URL = "https://cdn.casbin.org/site/casdoor/models";
const MODEL_URL = "https://cdn.casdoor.com/casdoor/models";
Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL),
faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL),
]).then((val) => {
setModelsLoaded(true);
}).catch((err) => {
message.error(i18next.t("login:Model loading failure"));
onCancel();
});
};
loadModels();
}, []);
React.useEffect(() => {
if (visible) {
setPercent(0);
if (modelsLoaded) {
navigator.mediaDevices
.getUserMedia({video: {facingMode: "user"}})
.then((stream) => {
mediaStreamRef.current = stream;
setIsCameraCaptured(true);
}).catch((error) => {
handleCameraError(error);
});
}
} else {
clearInterval(detection.current);
detection.current = null;
setIsCameraCaptured(false);
}
return () => {
clearInterval(detection.current);
detection.current = null;
setIsCameraCaptured(false);
};
}, [visible, modelsLoaded]);
React.useEffect(() => {
if (isCameraCaptured) {
let count = 0;
const interval = setInterval(() => {
count++;
if (videoRef.current) {
videoRef.current.srcObject = mediaStreamRef.current;
videoRef.current.play();
clearInterval(interval);
}
if (count >= 30) {
clearInterval(interval);
onCancel();
}
}, 100);
} else {
mediaStreamRef.current?.getTracks().forEach(track => track.stop());
if (videoRef.current) {
videoRef.current.srcObject = null;
}
}
}, [isCameraCaptured]);
const handleStreamVideo = () => {
let count = 0;
let goodCount = 0;
if (!detection.current) {
detection.current = setInterval(async() => {
if (modelsLoaded && videoRef.current && visible) {
const faces = await faceapi.detectAllFaces(videoRef.current, new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceDescriptors();
count++;
if (count % 50 === 0) {
message.warning(i18next.t("login:Please ensure sufficient lighting and align your face in the center of the recognition box"));
} else if (count > 300) {
message.error(i18next.t("login:Face recognition failed"));
onCancel();
}
if (faces.length === 1) {
const face = faces[0];
setPercent(Math.round(face.detection.score * 100));
const array = Array.from(face.descriptor);
if (face.detection.score > 0.9) {
goodCount++;
if (face.detection.score > 0.99 || goodCount > 10) {
clearInterval(detection.current);
onOk(array);
}
}
} else {
setPercent(Math.round(percent / 2));
}
}
}, 100);
}
};
const handleCameraError = (error) => {
onCancel();
if (error instanceof DOMException) {
if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
message.error(i18next.t("login:Please ensure that you have a camera device for facial recognition"));
} else if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
message.error(i18next.t("login:Please provide permission to access the camera"));
} else if (error.name === "NotReadableError" || error.name === "TrackStartError") {
message.error(i18next.t("login:The camera is currently in use by another webpage"));
} else if (error.name === "TypeError") {
message.error(i18next.t("login:Please load the webpage using HTTPS, otherwise the camera cannot be accessed"));
} else {
message.error(error.message);
}
}
};
return (
<div>
<Modal
closable={false}
maskClosable={false}
destroyOnClose={true}
open={visible && isCameraCaptured}
title={i18next.t("login:Face Recognition")}
width={350}
footer={[
<Button key="back" onClick={onCancel}>
Cancel
</Button>,
]}
>
<Progress percent={percent} />
<div style={{marginTop: "20px", marginBottom: "50px", justifyContent: "center", alignContent: "center", position: "relative", flexDirection: "column"}}>
{
modelsLoaded ?
<div style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
<video
ref={videoRef}
onPlay={handleStreamVideo}
style={{
borderRadius: "50%",
height: "220px",
verticalAlign: "middle",
width: "220px",
objectFit: "cover",
}}
></video>
<div style={{
position: "absolute",
width: "240px",
height: "240px",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}>
<svg width="240" height="240" fill="none">
<circle
strokeDasharray="700"
strokeDashoffset={700 - 6.9115 * percent}
strokeWidth="4"
cx="120"
cy="120"
r="110"
stroke="#5734d3"
transform="rotate(-90, 120, 120)"
strokeLinecap="round"
style={{transition: "all .2s linear"}}
></circle>
</svg>
</div>
<canvas ref={canvasRef} style={{position: "absolute"}} />
</div>
:
<div>
<Spin tip={i18next.t("login:Loading")} size="large" style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
<div className="content" />
</Spin>
</div>
}
</div>
</Modal>
</div>
);
};
export default FaceRecognitionModal;

View File

@ -27,9 +27,6 @@ code {
}
.logo {
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
background-size: 130px, 27px !important;
width: 130px;
height: 27px;
margin: 17px 0 16px 15px;
float: left;

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Datei erfolgreich hochgeladen",
"First, last": "First, last",
"Follow organization theme": "Folge dem Theme der Organisation",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Formposition",
"Form position - Tooltip": "Position der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare",
"Grant types": "Grant-Typen",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Liste erlaubter Umleitungs-URLs mit Unterstützung von regulärer Ausdrucksprüfung; URLs, die nicht in der Liste enthalten sind, können nicht umgeleitet werden",
"Refresh token expire": "Gültigkeitsdauer des Refresh-Tokens",
"Refresh token expire - Tooltip": "Angabe der Gültigkeitsdauer des Refresh Tokens",
"Reset to Empty": "Reset to Empty",
"Right": "Rechts",
"Rule": "Regel",
"SAML metadata": "SAML-Metadaten",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Tabellenname des Policy Stores",
"Adapters": "Adapter",
"Add": "Hinzufügen",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Zugehörigkeits-URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Gültige E-Mail-Adresse",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Fehler beim hinzufügen",
"Failed to connect to server": "Die Verbindung zum Server konnte nicht hergestellt werden",
"Failed to delete": "Konnte nicht gelöscht werden",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Symbole, die die Anwendung der Außenwelt präsentiert",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Hauptpasswort",
@ -452,6 +462,9 @@
"Continue with": "Weitermachen mit",
"Email": "Email",
"Email or phone": "E-Mail oder Telefon",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Passwort vergessen?",
@ -461,9 +474,12 @@
"Logging out...": "Ausloggen...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "Kein Konto?",
"Or sign in with another account": "Oder mit einem anderen Konto anmelden",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Bitte geben Sie Ihre E-Mail oder Telefonnummer ein!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Bitte geben Sie Ihren Code ein!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Umleitung, bitte warten.",
"Sign In": "Anmelden",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Melden Sie sich mit WebAuthn an",
"Sign in with {type}": "Melden Sie sich mit {type} an",
"Signing in...": "Anmelden...",
"Successfully logged in with WebAuthn credentials": "Erfolgreich mit WebAuthn-Anmeldeinformationen angemeldet",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "Die Eingabe ist keine gültige E-Mail-Adresse oder Telefonnummer!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "E-Mail darf nicht leer sein",
"Email/phone reset successfully": "E-Mail-/Telefon-Zurücksetzung erfolgreich durchgeführt",
"Empty input!": "Leere Eingabe!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Startseite des Benutzers",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Archivo subido exitosamente",
"First, last": "First, last",
"Follow organization theme": "Seguir el tema de la organización",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Posición de la Forma",
"Form position - Tooltip": "Ubicación de los formularios de registro, inicio de sesión y olvido de contraseña",
"Grant types": "Tipos de subvenciones",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Lista de URL de redireccionamiento permitidos, con soporte para coincidencias de expresiones regulares; las URL que no estén en la lista no se redirigirán",
"Refresh token expire": "Token de actualización expirado",
"Refresh token expire - Tooltip": "Tiempo de caducidad del token de actualización",
"Reset to Empty": "Reset to Empty",
"Right": "Correcto",
"Rule": "Regla",
"SAML metadata": "Metadatos de SAML",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Nombre de la tabla de la tienda de políticas",
"Adapters": "Adaptadores",
"Add": "Añadir",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "URL de afiliación",
@ -225,9 +230,12 @@
"Email - Tooltip": "Dirección de correo electrónico válida",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "No se pudo agregar",
"Failed to connect to server": "No se pudo conectar al servidor",
"Failed to delete": "No se pudo eliminar",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logotipo",
"Logo - Tooltip": "Iconos que la aplicación presenta al mundo exterior",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Contraseña maestra",
@ -452,6 +462,9 @@
"Continue with": "Continúe con",
"Email": "Email",
"Email or phone": "Correo electrónico o teléfono",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "¿Olvidaste tu contraseña?",
@ -461,9 +474,12 @@
"Logging out...": "Cerrando sesión...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "¿No tienes cuenta?",
"Or sign in with another account": "O inicia sesión con otra cuenta",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "¡Por favor introduzca su correo electrónico o teléfono!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "¡Por favor ingrese su código!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "¡Ingrese su contraseña, por favor!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirigiendo, por favor espera.",
"Sign In": "Iniciar sesión",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Iniciar sesión con WebAuthn",
"Sign in with {type}": "Inicia sesión con {tipo}",
"Signing in...": "Iniciando sesión...",
"Successfully logged in with WebAuthn credentials": "Inició sesión correctamente con las credenciales de WebAuthn",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "¡La entrada no es un correo electrónico o número de teléfono válido!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "El correo electrónico no puede estar vacío",
"Email/phone reset successfully": "Restablecimiento de correo electrónico/teléfono exitoso",
"Empty input!": "¡Entrada vacía!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Página de inicio del usuario",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Fichier téléchargé avec succès",
"First, last": "Prénom, nom",
"Follow organization theme": "Suivre le thème de l'organisation",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Position du formulaire",
"Form position - Tooltip": "Emplacement des formulaires d'inscription, de connexion et de récupération de mot de passe",
"Grant types": "Types d'autorisation",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Liste des URL de redirection autorisées, les expressions régulières sont supportées ; les URL n'étant pas dans la liste ne seront pas redirigées",
"Refresh token expire": "Expiration du jeton de rafraîchissement",
"Refresh token expire - Tooltip": "Durée avant expiration du jeton de rafraîchissement",
"Reset to Empty": "Reset to Empty",
"Right": "Droit",
"Rule": "Règle",
"SAML metadata": "Métadonnées SAML",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Nom de la table du magasin de règle",
"Adapters": "Adaptateurs",
"Add": "Ajouter",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Administration",
"Affiliation URL": "URL d'affiliation",
@ -225,9 +230,12 @@
"Email - Tooltip": "Adresse e-mail valide",
"Email only": "Email only",
"Enable": "Activer",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Activé",
"Enabled successfully": "Activé avec succès",
"Enforcers": "Exécuteurs",
"FaceIdData": "FaceIdData",
"Failed to add": "Échec d'ajout",
"Failed to connect to server": "Échec de la connexion au serveur",
"Failed to delete": "Échec de la suppression",
@ -264,6 +272,8 @@
"Logging & Auditing": "Journalisation et Audit",
"Logo": "Logo",
"Logo - Tooltip": "Icônes que l'application présente au monde extérieur",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "Type d'authentification multifacteur",
"MFA items - Tooltip": "Types d'authentification multifacteur - Infobulle",
"Master password": "Mot de passe passe-partout",
@ -452,6 +462,9 @@
"Continue with": "Continuer avec",
"Email": "Email",
"Email or phone": "Email ou téléphone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Échec de l'obtention de l'autorisation MetaMask",
"Failed to obtain Web3-Onboard authorization": "Échec de l'obtention de l'autorisation MetaMask",
"Forgot password?": "Mot de passe oublié ?",
@ -461,9 +474,12 @@
"Logging out...": "Déconnexion...",
"Login button": "Login button",
"MetaMask plugin not detected": "Le plugin MetaMask n'a pas été détecté",
"Model loading failure": "Model loading failure",
"No account?": "Pas de compte ?",
"Or sign in with another account": "Ou connectez-vous avec un autre compte",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Veuillez saisir votre adresse e-mail ou votre numéro de téléphone !",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Veuillez saisir votre code !",
"Please input your organization name!": "Veuillez saisir le nom de votre organisation !",
"Please input your password!": "Veuillez saisir votre mot de passe !",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Veuillez sélectionner une organisation",
"Please select an organization to sign in": "Veuillez choisir une organisation pour vous connecter",
"Please type an organization to sign in": "Veuillez entrer une organisation pour vous connecter",
"Redirecting, please wait.": "Redirection en cours, veuillez patienter.",
"Sign In": "Se connecter",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Connectez-vous avec WebAuthn",
"Sign in with {type}": "Connectez-vous avec {type}",
"Signing in...": "Connexion en cours...",
"Successfully logged in with WebAuthn credentials": "Connexion avec les identifiants WebAuthn réussie",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "L'entrée n'est pas une adresse e-mail ou un numéro de téléphone valide !",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Le champ e-mail doit être rempli",
"Email/phone reset successfully": "E-mail ou téléphone réinitialisé avec succès",
"Empty input!": "Champ vide !",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Genre",
"Gender - Tooltip": "Genre - Infobulle",
"Homepage": "Site web",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Berkas telah diunggah dengan sukses",
"First, last": "First, last",
"Follow organization theme": "Ikuti tema organisasi",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Posisi formulir",
"Form position - Tooltip": "Tempat pendaftaran, masuk, dan lupa kata sandi",
"Grant types": "Jenis-jenis hibah",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Daftar URL redirect yang diizinkan, mendukung pencocokan ekspresi reguler; URL yang tidak ada dalam daftar akan gagal dialihkan",
"Refresh token expire": "Token segar kedaluwarsa",
"Refresh token expire - Tooltip": "Waktu kedaluwarsa token penyegaran",
"Reset to Empty": "Reset to Empty",
"Right": "Benar",
"Rule": "Aturan",
"SAML metadata": "Metadata SAML",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Nama tabel dari penyimpanan kebijakan",
"Adapters": "Adaptor",
"Add": "Tambahkan",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "URL Afiliasi",
@ -225,9 +230,12 @@
"Email - Tooltip": "Alamat email yang valid",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Gagal menambahkan",
"Failed to connect to server": "Gagal terhubung ke server",
"Failed to delete": "Gagal menghapus",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Ikon-ikon yang disajikan aplikasi ke dunia luar",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Kata sandi utama",
@ -452,6 +462,9 @@
"Continue with": "Lanjutkan dengan",
"Email": "Email",
"Email or phone": "Email atau telepon",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Lupa kata sandi?",
@ -461,9 +474,12 @@
"Logging out...": "Keluar...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "Tidak memiliki akun?",
"Or sign in with another account": "Atau masuk dengan akun lain",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Silahkan masukkan email atau nomor telepon Anda!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Silakan masukkan kode Anda!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Masukkan kata sandi Anda!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Mengalihkan, harap tunggu.",
"Sign In": "Masuk",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Masuk dengan WebAuthn",
"Sign in with {type}": "Masuk dengan {type}",
"Signing in...": "Masuk...",
"Successfully logged in with WebAuthn credentials": "Berhasil masuk dengan kredensial WebAuthn",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "Input yang Anda masukkan tidak valid, tidak sesuai dengan Email atau nomor telepon!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email tidak boleh kosong",
"Email/phone reset successfully": "Email/telepon berhasil diatur ulang",
"Empty input!": "Masukan kosong!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "ファイルが正常にアップロードされました",
"First, last": "First, last",
"Follow organization theme": "組織のテーマに従ってください",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "フォームのポジション",
"Form position - Tooltip": "登録、ログイン、パスワード忘れフォームの位置",
"Grant types": "グラント種類",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "許可されたリダイレクトURLリストは、正規表現マッチングをサポートしています。リストに含まれていないURLはリダイレクトできません",
"Refresh token expire": "リフレッシュトークンの有効期限が切れました",
"Refresh token expire - Tooltip": "リフレッシュトークンの有効期限時間",
"Reset to Empty": "Reset to Empty",
"Right": "右",
"Rule": "ルール",
"SAML metadata": "SAMLメタデータ",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "ポリシー・ストアのテーブル名",
"Adapters": "アダプター",
"Add": "追加",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "所属するURL",
@ -225,9 +230,12 @@
"Email - Tooltip": "有効な電子メールアドレス",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "追加できませんでした",
"Failed to connect to server": "サーバーに接続できませんでした",
"Failed to delete": "削除に失敗しました",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "ロゴ",
"Logo - Tooltip": "アプリケーションが外部世界に示すアイコン",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "マスターパスワード",
@ -452,6 +462,9 @@
"Continue with": "続ける",
"Email": "Email",
"Email or phone": "メールまたは電話",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "パスワードを忘れましたか?",
@ -461,9 +474,12 @@
"Logging out...": "ログアウト中...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "アカウントがありませんか?",
"Or sign in with another account": "別のアカウントでサインインする",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "あなたのメールアドレスまたは電話番号を入力してください!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "あなたのコードを入力してください!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "パスワードを入力してください!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "リダイレクト中、お待ちください。",
"Sign In": "サインイン",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "WebAuthnでサインインしてください",
"Sign in with {type}": "{type}でサインインしてください",
"Signing in...": "サインイン中...",
"Successfully logged in with WebAuthn credentials": "WebAuthnの認証情報で正常にログインしました",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "入力されたのは有効なメールアドレスまたは電話番号ではありません",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "電子メールは空にできません",
"Email/phone reset successfully": "メール/電話のリセットが成功しました",
"Empty input!": "空の入力!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "ユーザーのホームページ",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "파일이 성공적으로 업로드되었습니다",
"First, last": "First, last",
"Follow organization theme": "조직의 주제를 따르세요",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "양식 위치",
"Form position - Tooltip": "가입, 로그인 및 비밀번호 재설정 양식의 위치",
"Grant types": "Grant types: 부여 유형",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "허용된 리디렉션 URL 목록은 정규 표현식 일치를 지원합니다. 목록에 없는 URL은 리디렉션에 실패합니다",
"Refresh token expire": "리프레시 토큰 만료",
"Refresh token expire - Tooltip": "리프레시 토큰 만료 시간",
"Reset to Empty": "Reset to Empty",
"Right": "옳은",
"Rule": "규칙",
"SAML metadata": "SAML 메타데이터",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "정책 저장소의 테이블 이름",
"Adapters": "어댑터",
"Add": "추가하다",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "소속 URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "유효한 이메일 주소",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "추가하지 못했습니다",
"Failed to connect to server": "서버에 연결하지 못했습니다",
"Failed to delete": "삭제에 실패했습니다",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "로고",
"Logo - Tooltip": "애플리케이션이 외부 세계에 제시하는 아이콘들",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "마스터 비밀번호",
@ -452,6 +462,9 @@
"Continue with": "계속하다",
"Email": "Email",
"Email or phone": "이메일 또는 전화",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "비밀번호를 잊으셨나요?",
@ -461,9 +474,12 @@
"Logging out...": "로그아웃 중...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "계정이 없나요?",
"Or sign in with another account": "다른 계정으로 로그인하세요",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "이메일 또는 전화번호를 입력해주세요!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "코드를 입력해주세요!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "비밀번호를 입력해주세요!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "리디렉팅 중입니다. 잠시 기다려주세요.",
"Sign In": "로그인",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "WebAuthn으로 로그인하세요",
"Sign in with {type}": "{type}로 로그인하세요",
"Signing in...": "로그인 중...",
"Successfully logged in with WebAuthn credentials": "WebAuthn 자격 증명으로 로그인 성공적으로 수행했습니다",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "입력한 값은 유효한 이메일 또는 전화번호가 아닙니다!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "이메일은 비어 있을 수 없습니다",
"Email/phone reset successfully": "이메일/전화 초기화가 성공적으로 완료되었습니다",
"Empty input!": "빈 입력!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "사용자의 홈페이지",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Arquivo enviado com sucesso",
"First, last": "Primeiro, último",
"Follow organization theme": "Seguir tema da organização",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Posição do formulário",
"Form position - Tooltip": "Localização dos formulários de registro, login e recuperação de senha",
"Grant types": "Tipos de concessão",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Lista de URLs de redirecionamento permitidos, com suporte à correspondência por expressões regulares; URLs que não estão na lista falharão ao redirecionar",
"Refresh token expire": "Expiração do token de atualização",
"Refresh token expire - Tooltip": "Tempo de expiração do token de atualização",
"Reset to Empty": "Reset to Empty",
"Right": "Direita",
"Rule": "Regra",
"SAML metadata": "Metadados do SAML",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Nome da tabela do armazenamento de políticas",
"Adapters": "Adaptadores",
"Add": "Adicionar",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "URL da Afiliação",
@ -225,9 +230,12 @@
"Email - Tooltip": "Endereço de e-mail válido",
"Email only": "Email only",
"Enable": "Habilitar",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Habilitado",
"Enabled successfully": "Habilitado com sucesso",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Falha ao adicionar",
"Failed to connect to server": "Falha ao conectar ao servidor",
"Failed to delete": "Falha ao excluir",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Ícones que o aplicativo apresenta para o mundo externo",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Senha mestra",
@ -452,6 +462,9 @@
"Continue with": "Continuar com",
"Email": "Email",
"Email or phone": "Email ou telefone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Esqueceu a senha?",
@ -461,9 +474,12 @@
"Logging out...": "Saindo...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "Não possui uma conta?",
"Or sign in with another account": "Ou entre com outra conta",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Por favor, informe seu email ou telefone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Por favor, informe o código!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Por favor, informe sua senha!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecionando, por favor aguarde.",
"Sign In": "Entrar",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Entrar com WebAuthn",
"Sign in with {type}": "Entrar com {type}",
"Signing in...": "Entrando...",
"Successfully logged in with WebAuthn credentials": "Logado com sucesso usando credenciais WebAuthn",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "O valor inserido não é um email ou número de telefone válido!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "O e-mail não pode ficar em branco",
"Email/phone reset successfully": "Redefinição de e-mail/telefone com sucesso",
"Empty input!": "Entrada vazia!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gênero",
"Gender - Tooltip": "Gênero - Tooltip",
"Homepage": "Página inicial",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Файл успешно загружен",
"First, last": "Имя, Фамилия",
"Follow organization theme": "Cледуйте теме организации",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Позиция формы",
"Form position - Tooltip": "Местоположение форм регистрации, входа и восстановления пароля",
"Grant types": "Типы грантов",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Разрешенный список URL-адресов для перенаправления с поддержкой сопоставления регулярных выражений; URL-адреса, которые не находятся в списке, не будут перенаправляться",
"Refresh token expire": "Срок действия токена обновления истек",
"Refresh token expire - Tooltip": "Время истечения токена обновления",
"Reset to Empty": "Reset to Empty",
"Right": "Правильно",
"Rule": "Правило",
"SAML metadata": "Метаданные SAML",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Имя таблицы хранилища политик",
"Adapters": "Адаптеры",
"Add": "Добавить",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "URL принадлежности",
@ -225,9 +230,12 @@
"Email - Tooltip": "Действительный адрес электронной почты",
"Email only": "Email only",
"Enable": "Включить",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Включено",
"Enabled successfully": "Успешно включено",
"Enforcers": "Контролёры доступа",
"FaceIdData": "FaceIdData",
"Failed to add": "Не удалось добавить",
"Failed to connect to server": "Не удалось подключиться к серверу",
"Failed to delete": "Не удалось удалить",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Логотип",
"Logo - Tooltip": "Иконки, которые приложение представляет во внешний мир",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Главный пароль",
@ -452,6 +462,9 @@
"Continue with": "Продолжайте с",
"Email": "Email",
"Email or phone": "Электронная почта или телефон",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Не удалось получить авторизацию MetaMask",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Забыли пароль?",
@ -461,9 +474,12 @@
"Logging out...": "Выход...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "Нет аккаунта?",
"Or sign in with another account": "Или войти с другой учетной записью",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Пожалуйста, введите свой адрес электронной почты или номер телефона!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Пожалуйста, введите свой код!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Пожалуйста, введите свой пароль!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Перенаправление, пожалуйста, подождите.",
"Sign In": "Войти",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Войти с помощью WebAuthn",
"Sign in with {type}": "Войти с помощью {type}",
"Signing in...": "Вход в систему...",
"Successfully logged in with WebAuthn credentials": "Успешный вход с учетными данными WebAuthn",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "Ввод не является действительным адресом электронной почты или телефонным номером!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email не может быть пустым",
"Email/phone reset successfully": "Электронная почта / номер телефона успешно сброшены",
"Empty input!": "Пустой ввод!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Главная страница пользователя",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "Adı, Soyadı",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Kabul edilen yönlendirme URL listesi, düzenli ifadeleri (regexp) kullanabilirsiniz. Eğer url bu lşistede yoksa hata sayfasına yönlendirilirsiniz",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Sağ",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Ekle",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Geçerli e-posta adresi",
"Email only": "Sadece eposta",
"Enable": "Etkinleştir",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Etkin",
"Enabled successfully": "Başarıyla etkinleştirildi",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Ekleme başarısız oldu.",
"Failed to connect to server": "Sunucuya bağlanılamıyor",
"Failed to delete": "Silme başarısız oldu",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "İle devam et",
"Email": "E-Posta",
"Email or phone": "E-posta veya telefon",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Metamask yetkilendirmesi başarısız",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Şifrenizi mi unuttunuz?",
@ -461,9 +474,12 @@
"Logging out...": ıkış yapılıyor...",
"Login button": "Login button",
"MetaMask plugin not detected": "Metamask plugin-in bulunamadı",
"Model loading failure": "Model loading failure",
"No account?": "Hesabınız yok mu?",
"Or sign in with another account": "Veya başka bir hesapla giriş yapın",
"Phone": "Telefon",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Lütfen Telefon numaranızı ya da E-Posta adresinizi giriniz!",
"Please input your Email!": "Eposta adresinizi girin!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Lütfen size gönderilen kodu girin!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Lütfen şifrenizi girin!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Yönlendiriliyor, lütfen bekleyiniz.",
"Sign In": "Oturum aç",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "{type} ile giriş yap",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "File uploaded successfully",
"First, last": "First, last",
"Follow organization theme": "Follow organization theme",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Form position",
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
"Grant types": "Grant types",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Allowed redirect URL list, supporting regular expression matching; URLs not in the list will fail to redirect",
"Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expiration time",
"Reset to Empty": "Reset to Empty",
"Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Table name of the policy store",
"Adapters": "Adapters",
"Add": "Add",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
@ -225,9 +230,12 @@
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Logo",
"Logo - Tooltip": "Icons that the application presents to the outside world",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Master password",
@ -452,6 +462,9 @@
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
@ -461,9 +474,12 @@
"Logging out...": "Logging out...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email cannot be empty",
"Email/phone reset successfully": "Email/phone reset successfully",
"Empty input!": "Empty input!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Homepage",

View File

@ -58,6 +58,9 @@
"File uploaded successfully": "Tệp được tải lên thành công",
"First, last": "Tên, Họ",
"Follow organization theme": "Theo giao diện tổ chức",
"Footer HTML": "Footer HTML",
"Footer HTML - Edit": "Footer HTML - Edit",
"Footer HTML - Tooltip": "Custom the footer of your application",
"Form position": "Vị trí của hình thức",
"Form position - Tooltip": "Vị trí của các biểu mẫu đăng ký, đăng nhập và quên mật khẩu",
"Grant types": "Loại hỗ trợ",
@ -88,6 +91,7 @@
"Redirect URLs - Tooltip": "Danh sách URL chuyển hướng được phép, hỗ trợ khớp biểu thức chính quy; các URL không có trong danh sách sẽ không được chuyển hướng",
"Refresh token expire": "Làm mới mã thông báo hết hạn",
"Refresh token expire - Tooltip": "Thời gian hết hạn của mã thông báo làm mới",
"Reset to Empty": "Reset to Empty",
"Right": "Đúng",
"Rule": "Quy tắc",
"SAML metadata": "SAML metadata: Siêu dữ liệu SAML",
@ -177,6 +181,7 @@
"Adapter - Tooltip": "Tên bảng của kho lưu trữ chính sách",
"Adapters": "Bộ chuyển đổi",
"Add": "Tạo mới",
"Add Face Id": "Add Face Id",
"Add custom item": "Add custom item",
"Admin": "Admin",
"Affiliation URL": "Đường dẫn liên kết liên kết",
@ -225,9 +230,12 @@
"Email - Tooltip": "Địa chỉ email hợp lệ",
"Email only": "Email only",
"Enable": "Enable",
"Enable dark logo": "Enable dark logo",
"Enable dark logo - Tooltip": "Enable dark logo",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
"Enforcers": "Enforcers",
"FaceIdData": "FaceIdData",
"Failed to add": "Không thể thêm được",
"Failed to connect to server": "Không thể kết nối đến máy chủ",
"Failed to delete": "Không thể xoá",
@ -264,6 +272,8 @@
"Logging & Auditing": "Logging & Auditing",
"Logo": "Biểu tượng",
"Logo - Tooltip": "Biểu tượng mà ứng dụng hiển thị ra ngoài thế giới",
"Logo dark": "Logo dark",
"Logo dark - Tooltip": "The logo used in dark theme",
"MFA items": "MFA items",
"MFA items - Tooltip": "MFA items - Tooltip",
"Master password": "Mật khẩu chính",
@ -452,6 +462,9 @@
"Continue with": "Tiếp tục với",
"Email": "Email",
"Email or phone": "Email hoặc điện thoại",
"Face ID": "Face ID",
"Face Recognition": "Face Recognition",
"Face recognition failed": "Face recognition failed",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Quên mật khẩu?",
@ -461,9 +474,12 @@
"Logging out...": "Đăng xuất ...",
"Login button": "Login button",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"Model loading failure": "Model loading failure",
"No account?": "Không có tài khoản?",
"Or sign in with another account": "Hoặc đăng nhập bằng tài khoản khác",
"Phone": "Phone",
"Please ensure sufficient lighting and align your face in the center of the recognition box": "Please ensure sufficient lighting and align your face in the center of the recognition box",
"Please ensure that you have a camera device for facial recognition": "Please ensure that you have a camera device for facial recognition",
"Please input your Email or Phone!": "Vui lòng nhập địa chỉ Email hoặc số điện thoại của bạn!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
@ -471,15 +487,19 @@
"Please input your code!": "Vui lòng nhập mã của bạn!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Vui lòng nhập mật khẩu của bạn!",
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "Please load the webpage using HTTPS, otherwise the camera cannot be accessed",
"Please provide permission to access the camera": "Please provide permission to access the camera",
"Please select an organization": "Please select an organization",
"Please select an organization to sign in": "Please select an organization to sign in",
"Please type an organization to sign in": "Please type an organization to sign in",
"Redirecting, please wait.": "Đang chuyển hướng, vui lòng đợi.",
"Sign In": "Đăng nhập",
"Sign in with Face ID": "Sign in with Face ID",
"Sign in with WebAuthn": "Đăng nhập với WebAuthn",
"Sign in with {type}": "Đăng nhập bằng {type}",
"Signing in...": "Đăng nhập...",
"Successfully logged in with WebAuthn credentials": "Đã đăng nhập thành công với thông tin WebAuthn",
"The camera is currently in use by another webpage": "The camera is currently in use by another webpage",
"The input is not valid Email or phone number!": "Đầu vào không phải là địa chỉ Email hoặc số điện thoại hợp lệ!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
@ -1061,6 +1081,8 @@
"Email cannot be empty": "Email không được để trống",
"Email/phone reset successfully": "Đặt lại email/điện thoại thành công",
"Empty input!": "Đầu vào trống!",
"Face ID": "Face ID",
"Face IDs": "Face IDs",
"Gender": "Gender",
"Gender - Tooltip": "Gender - Tooltip",
"Homepage": "Trang chủ của người dùng",

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