mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 17:10:27 +08:00
Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
72839d6bf5 | |||
2c4b1093ed | |||
d1c55d5aa7 | |||
c8aa35c9c6 | |||
6037f37b87 | |||
1b478903d8 | |||
4f5ac7a10b | |||
e81ba62234 | |||
a19060c7cb | |||
96812f676b | |||
04f0458b5c | |||
fd0bcd9a17 | |||
01a5958307 | |||
be88b00278 | |||
1bd0245e7a | |||
cc84bd37cf | |||
8302fcf805 | |||
391a533ce1 | |||
57431a59ad | |||
88a4736520 | |||
2cb6ff69ae | |||
e1e5943a3e | |||
3875896c1e | |||
7e2f265420 | |||
53ef179e9b | |||
376ef0ed14 | |||
ca183be336 | |||
e5da57a005 | |||
e4e225db32 | |||
a1add992ee | |||
2aac265ed4 | |||
2dc755f529 | |||
0dd474d5fc | |||
6998451e97 | |||
9175e5b664 | |||
dbc6b0dc45 | |||
31b7000f6a | |||
d25eaa65cd | |||
f5bcd00652 | |||
0d5f49e40a | |||
3527e070a0 | |||
0108b58db4 | |||
976b5766a5 | |||
a92d20162a | |||
204b1c2b8c | |||
49fb269170 | |||
c532a5d54d | |||
89df80baca | |||
d988ac814c | |||
e4b25055d5 |
@ -161,6 +161,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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
token := c.GetSessionToken()
|
||||
if token == nil {
|
||||
token, err = object.GetTokenForExtension(user, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.SetSessionToken(token)
|
||||
}
|
||||
u.AccessToken = token.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()
|
||||
|
@ -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
|
||||
|
@ -122,6 +122,17 @@ func (c *ApiController) GetSessionUsername() string {
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionToken() *object.Token {
|
||||
tokenValue := c.GetSession("token")
|
||||
var token *object.Token
|
||||
var ok bool
|
||||
if token, ok = tokenValue.(*object.Token); !ok {
|
||||
token = nil
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionApplication() *object.Application {
|
||||
clientId := c.GetSession("aud")
|
||||
if clientId == nil {
|
||||
@ -141,6 +152,10 @@ func (c *ApiController) ClearUserSession() {
|
||||
c.SetSessionData(nil)
|
||||
}
|
||||
|
||||
func (c *ApiController) ClearTokenSession() {
|
||||
c.SetSessionToken(nil)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
@ -167,6 +182,10 @@ func (c *ApiController) SetSessionUsername(user string) {
|
||||
c.SetSession("username", user)
|
||||
}
|
||||
|
||||
func (c *ApiController) SetSessionToken(token *object.Token) {
|
||||
c.SetSession("token", token)
|
||||
}
|
||||
|
||||
// GetSessionData ...
|
||||
func (c *ApiController) GetSessionData() *SessionData {
|
||||
session := c.GetSession("SessionData")
|
||||
|
@ -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{}
|
||||
|
@ -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 {
|
||||
|
@ -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,10 +111,12 @@ func (c *ApiController) SendEmail() {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
content = strings.Replace(provider.Content, "%s", code, 1)
|
||||
if !strings.HasPrefix(userId, "app/") {
|
||||
var user *object.User
|
||||
user, err = object.GetUser(userId)
|
||||
@ -128,6 +129,7 @@ func (c *ApiController) SendEmail() {
|
||||
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "邀请码使用次数已耗尽",
|
||||
|
@ -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
|
||||
|
@ -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,13 +532,14 @@ 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
|
||||
if !application.OrganizationObj.IsProfilePublic {
|
||||
application.OrganizationObj.AccountItems = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return application
|
||||
}
|
||||
@ -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 {
|
||||
|
@ -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 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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -45,6 +45,7 @@ func InitDb() {
|
||||
}
|
||||
|
||||
initWebAuthn()
|
||||
initToken()
|
||||
}
|
||||
|
||||
func getBuiltInAccountItems() []*AccountItem {
|
||||
@ -184,6 +185,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 +199,7 @@ func initBuiltInApplication() {
|
||||
},
|
||||
Tags: []string{},
|
||||
RedirectUris: []string{},
|
||||
TokenFormat: "JWT",
|
||||
TokenFields: []string{},
|
||||
ExpireInHours: 168,
|
||||
FormOffset: 2,
|
||||
@ -307,6 +310,10 @@ func initWebAuthn() {
|
||||
gob.Register(webauthn.SessionData{})
|
||||
}
|
||||
|
||||
func initToken() {
|
||||
gob.Register(&Token{})
|
||||
}
|
||||
|
||||
func initBuiltInUserModel() {
|
||||
model, err := GetModel("built-in/user-model-built-in")
|
||||
if err != nil {
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
|
@ -179,7 +179,7 @@ type IdpSSODescriptor struct {
|
||||
}
|
||||
|
||||
type NameIDFormat struct {
|
||||
XMLName xml.Name
|
||||
// XMLName xml.Name
|
||||
Value string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
714
object/token.go
714
object/token.go
@ -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,7 +137,9 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func GetTokenByTokenValue(tokenValue string) (*Token, error) {
|
||||
func GetTokenByTokenValue(tokenValue, tokenTypeHint string) (*Token, error) {
|
||||
switch tokenTypeHint {
|
||||
case "access_token":
|
||||
token, err := GetTokenByAccessToken(tokenValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -194,14 +147,15 @@ func GetTokenByTokenValue(tokenValue string) (*Token, error) {
|
||||
if token != nil {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
token, err = GetTokenByRefreshToken(tokenValue)
|
||||
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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
744
object/token_oauth.go
Normal file
744
object/token_oauth.go
Normal file
@ -0,0 +1,744 @@
|
||||
// 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 GetTokenForExtension(user *User, host string) (*Token, error) {
|
||||
application, err := GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if application == nil {
|
||||
return nil, fmt.Errorf("the application for user %s is not found", user.Id)
|
||||
}
|
||||
|
||||
token, err := GetTokenByUser(application, user, "profile", "", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return token, nil
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"`
|
||||
|
@ -66,11 +66,19 @@ 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") == "/" {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !(strings.HasPrefix(ctx.Request.URL.Path, "/api/get-") && strings.HasSuffix(ctx.Request.URL.Path, "s")) {
|
||||
|
@ -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) {
|
||||
|
@ -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]
|
||||
|
@ -93,6 +93,7 @@ module.exports = {
|
||||
"buffer": false,
|
||||
"crypto": false,
|
||||
"os": false,
|
||||
"fs": false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
}
|
||||
}>
|
||||
{
|
||||
this.state.application?.footerHtml && this.state.application.footerHtml !== "" ?
|
||||
<React.Fragment>
|
||||
<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}
|
||||
|
@ -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) => {
|
||||
|
@ -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"},
|
||||
|
@ -171,32 +171,36 @@ 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>
|
||||
{
|
||||
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"))} :
|
||||
@ -213,6 +217,8 @@ class CertEditPage extends React.Component {
|
||||
</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"))} :
|
||||
|
@ -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) => {
|
||||
|
@ -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"}}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
|
@ -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"))} :
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
}
|
||||
{
|
||||
(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")}
|
||||
{this.state.mode === "add" ? i18next.t("user:New User") : (this.isSelf() ? i18next.t("account:My Account") : i18next.t("user:Edit User"))}
|
||||
<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,8 +1119,10 @@ class UserEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (location.pathname !== "/account") {
|
||||
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (exitAfterSave) {
|
||||
if (this.state.returnUrl) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,13 @@ class LoginPage extends React.Component {
|
||||
this.signInWithWebAuthn(username, values);
|
||||
return;
|
||||
}
|
||||
if (this.state.loginMethod === "faceId") {
|
||||
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 +533,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}} />
|
||||
@ -648,6 +669,25 @@ class LoginPage extends React.Component {
|
||||
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 +752,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 +1060,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 +1154,7 @@ class LoginPage extends React.Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{height: 300, width: 300}}>
|
||||
<div style={{height: 300}}>
|
||||
{renderChoiceBox()}
|
||||
</div>
|
||||
);
|
||||
@ -1178,7 +1224,6 @@ class LoginPage extends React.Component {
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
</div>
|
||||
<div className="login-form">
|
||||
<div>
|
||||
<div>
|
||||
{
|
||||
this.renderLoginPanel(application)
|
||||
@ -1187,7 +1232,6 @@ class LoginPage extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
220
web/src/common/modal/FaceRecognitionModal.js
Normal file
220
web/src/common/modal/FaceRecognitionModal.js
Normal 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;
|
@ -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;
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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": "ユーザーのホームページ",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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": "사용자의 홈페이지",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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": "Главная страница пользователя",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -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,6 +487,8 @@
|
||||
"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",
|
||||
@ -480,6 +498,7 @@
|
||||
"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 +1080,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",
|
||||
|
@ -58,6 +58,9 @@
|
||||
"File uploaded successfully": "文件上传成功",
|
||||
"First, last": "名字, 姓氏",
|
||||
"Follow organization theme": "使用组织主题",
|
||||
"Footer HTML": "Footer HTML",
|
||||
"Footer HTML - Edit": "Footer HTML - Edit",
|
||||
"Footer HTML - Tooltip": "自定义应用的footer",
|
||||
"Form position": "表单位置",
|
||||
"Form position - Tooltip": "注册、登录、忘记密码等表单的位置",
|
||||
"Grant types": "OAuth授权类型",
|
||||
@ -88,6 +91,7 @@
|
||||
"Redirect URLs - Tooltip": "允许的重定向URL列表,支持正则匹配,不在列表中的URL将会跳转失败",
|
||||
"Refresh token expire": "Refresh Token过期",
|
||||
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
||||
"Reset to Empty": "重置为空",
|
||||
"Right": "居右",
|
||||
"Rule": "规则",
|
||||
"SAML metadata": "SAML元数据",
|
||||
@ -177,6 +181,7 @@
|
||||
"Adapter - Tooltip": "策略存储的表名",
|
||||
"Adapters": "Casbin适配器",
|
||||
"Add": "添加",
|
||||
"Add Face Id": "添加人脸",
|
||||
"Add custom item": "添加自定义项",
|
||||
"Admin": "管理工具",
|
||||
"Affiliation URL": "工作单位URL",
|
||||
@ -225,9 +230,12 @@
|
||||
"Email - Tooltip": "合法的电子邮件地址",
|
||||
"Email only": "仅支持邮件",
|
||||
"Enable": "启用",
|
||||
"Enable dark logo": "开启暗黑Logo",
|
||||
"Enable dark logo - Tooltip": "开启暗黑Logo",
|
||||
"Enabled": "已开启",
|
||||
"Enabled successfully": "启用成功",
|
||||
"Enforcers": "Casbin执行器",
|
||||
"FaceIdData": "人脸数据",
|
||||
"Failed to add": "添加失败",
|
||||
"Failed to connect to server": "连接服务器失败",
|
||||
"Failed to delete": "删除失败",
|
||||
@ -264,6 +272,8 @@
|
||||
"Logging & Auditing": "日志 & 审计",
|
||||
"Logo": "Logo",
|
||||
"Logo - Tooltip": "应用程序向外展示的图标",
|
||||
"Logo dark": "暗黑logo",
|
||||
"Logo dark - Tooltip": "暗黑主题下使用的logo",
|
||||
"MFA items": "MFA 项",
|
||||
"MFA items - Tooltip": "MFA 项 - Tooltip",
|
||||
"Master password": "万能密码",
|
||||
@ -452,6 +462,9 @@
|
||||
"Continue with": "使用以下账号继续",
|
||||
"Email": "Email",
|
||||
"Email or phone": "Email或手机号",
|
||||
"Face ID": "Face ID",
|
||||
"Face Recognition": "人脸识别",
|
||||
"Face recognition failed": "人脸识别失败",
|
||||
"Failed to obtain MetaMask authorization": "获取MetaMask授权失败",
|
||||
"Failed to obtain Web3-Onboard authorization": "获取 Web3-Onboard 授权失败",
|
||||
"Forgot password?": "忘记密码?",
|
||||
@ -461,9 +474,12 @@
|
||||
"Logging out...": "正在退出登录...",
|
||||
"Login button": "登录按钮",
|
||||
"MetaMask plugin not detected": "未检测到MetaMask插件",
|
||||
"Model loading failure": "人脸识别模型加载失败",
|
||||
"No account?": "没有账号?",
|
||||
"Or sign in with another account": "或者,登录其他账号",
|
||||
"Phone": "手机号",
|
||||
"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 input your Email or Phone!": "请输入您的Email或手机号!",
|
||||
"Please input your Email!": "请输入您的Email!",
|
||||
"Please input your LDAP username!": "请输入您的LDAP用户名!",
|
||||
@ -471,6 +487,8 @@
|
||||
"Please input your code!": "请输入您的验证码!",
|
||||
"Please input your organization name!": "请输入组织的名字!",
|
||||
"Please input your password!": "请输入您的密码!",
|
||||
"Please load the webpage using HTTPS, otherwise the camera cannot be accessed": "请使用HTTPS加载网页,否则无法使用摄像头",
|
||||
"Please provide permission to access the camera": "请打开摄像头访问权限",
|
||||
"Please select an organization": "请选择一个组织",
|
||||
"Please select an organization to sign in": "请选择要登录的组织",
|
||||
"Please type an organization to sign in": "请输入要登录的组织",
|
||||
@ -480,6 +498,7 @@
|
||||
"Sign in with {type}": "{type}登录",
|
||||
"Signing in...": "正在登录...",
|
||||
"Successfully logged in with WebAuthn credentials": "成功使用WebAuthn证书登录",
|
||||
"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 phone number!": "您输入的手机号有误!",
|
||||
@ -1061,6 +1080,8 @@
|
||||
"Email cannot be empty": "邮箱不能为空",
|
||||
"Email/phone reset successfully": "邮箱或手机号重置成功",
|
||||
"Empty input!": "输入为空!",
|
||||
"Face ID": "Face ID",
|
||||
"Face IDs": "Face IDs",
|
||||
"Gender": "性别",
|
||||
"Gender - Tooltip": "性别 - Tooltip",
|
||||
"Homepage": "个人主页",
|
||||
|
@ -105,6 +105,7 @@ class AccountTable extends React.Component {
|
||||
{name: "Multi-factor authentication", label: i18next.t("user:Multi-factor authentication")},
|
||||
{name: "WebAuthn credentials", label: i18next.t("user:WebAuthn credentials")},
|
||||
{name: "Managed accounts", label: i18next.t("user:Managed accounts")},
|
||||
{name: "Face ID", label: i18next.t("user:Face ID")},
|
||||
];
|
||||
};
|
||||
|
||||
|
134
web/src/table/FaceIdTable.js
Normal file
134
web/src/table/FaceIdTable.js
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React, {Suspense, lazy} from "react";
|
||||
import {Button, Col, Input, Row, Table} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as Setting from "../Setting";
|
||||
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
|
||||
|
||||
class FaceIdTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
openFaceRecognitionModal: false,
|
||||
};
|
||||
}
|
||||
|
||||
updateTable(table) {
|
||||
this.props.onUpdateTable(table);
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
deleteRow(table, i) {
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
addFaceId(table, faceIdData) {
|
||||
const faceId = {
|
||||
name: Setting.getRandomName(),
|
||||
faceIdData: faceIdData,
|
||||
};
|
||||
if (table === undefined || table === null) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, faceId);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input defaultValue={text} onChange={e => {
|
||||
this.updateField(table, index, "name", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:FaceIdData"),
|
||||
dataIndex: "faceIdData",
|
||||
key: "faceIdData",
|
||||
render: (text, record, index) => {
|
||||
const front = text.slice(0, 3).join(", ");
|
||||
const back = text.slice(-3).join(", ");
|
||||
return "[" + front + " ... " + back + "]";
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
key: "action",
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="primary" danger onClick={() => {this.deleteRow(table, index);}}>
|
||||
{i18next.t("general:Delete")}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("user:Face IDs")}
|
||||
<Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true})}>
|
||||
{i18next.t("general:Add Face Id")}
|
||||
</Button>
|
||||
<Suspense fallback={null}>
|
||||
<FaceRecognitionModal
|
||||
visible={this.state.openFaceRecognitionModal}
|
||||
onOk={(faceIdData) => {
|
||||
this.addFaceId(table, faceIdData);
|
||||
this.setState({openFaceRecognitionModal: false});
|
||||
}}
|
||||
onCancel={() => this.setState({openFaceRecognitionModal: false})}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col span={24}>
|
||||
{
|
||||
this.renderTable(this.props.table)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FaceIdTable;
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {DeleteOutlined, DownOutlined, LinkOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Input, Row, Select, Table, Tooltip} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
@ -98,14 +98,27 @@ class ManagedAccountTable extends React.Component {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Signin URL"),
|
||||
dataIndex: "signinUrl",
|
||||
key: "signinUrl",
|
||||
// width: "420px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input prefix={<LinkOutlined />} value={text} onChange={e => {
|
||||
this.updateField(table, index, "signinUrl", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("signup:Username"),
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
width: "420px",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input defaultValue={text} onChange={e => {
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "username", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@ -115,10 +128,10 @@ class ManagedAccountTable extends React.Component {
|
||||
title: i18next.t("general:Password"),
|
||||
dataIndex: "password",
|
||||
key: "password",
|
||||
width: "420px",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input defaultValue={text} onChange={e => {
|
||||
<Input.Password value={text} onChange={e => {
|
||||
this.updateField(table, index, "password", e.target.value);
|
||||
}} />
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user