Compare commits

...

20 Commits

Author SHA1 Message Date
a120734bb1 feat: support links in email to reset password (#3939) 2025-07-12 00:18:56 +08:00
edd0b30e08 feat: Supports smooth migration of password hash (#3940) 2025-07-11 19:57:55 +08:00
2da597b26f feat: add support for per-account MFA validity period in org setting to reduce repeated prompts (#3917) 2025-07-11 00:24:33 +08:00
ef14c84edc feat: show the popover on the top when window's width too small and close popover when password options is empty (#3952) 2025-07-10 19:56:05 +08:00
cb5c7667b5 feat: change Subscription's StartTime and EndTime to string 2025-07-10 14:11:40 +08:00
920ed87f75 fix: refactor the code in CheckPassword() 2025-07-10 00:49:13 +08:00
6598f0ccdf feat: use token's client ID instead in IntrospectToken() API (#3948) 2025-07-09 22:07:44 +08:00
8e71e23d75 feat: improve error message for GetConfigInt64() 2025-07-09 00:32:00 +08:00
146a369f80 feat: improve error handling in AutoSigninFilter 2025-07-08 23:47:14 +08:00
9bbe5afb7c feat: use only one salt arg in CredManager.IsPasswordCorrect() (#3936) 2025-07-07 17:56:25 +08:00
b42391c6ce feat: move needUpdatePassword to response's Data3 field to avoid refresh token conflict (#3931) 2025-07-05 22:48:44 +08:00
fb035a5353 feat: CredManager.GetHashedPassword() only contains one salt arg now (#3928) 2025-07-05 18:41:37 +08:00
b1f68a60a4 feat: set createDatabase to false in TestDumpToFile() (#3924) 2025-07-03 22:50:23 +08:00
201d704a31 feat: improve TikTok username generation logic (#3923) 2025-07-03 20:53:15 +08:00
bf91ad6c97 feat: add Internet-Only captcha rule (#3919) 2025-07-03 02:39:06 +08:00
3ccc0339c7 feat: improve CheckToEnableCaptcha() logic 2025-07-03 02:32:07 +08:00
1f2b0a3587 feat: add user's MFA items (#3921) 2025-07-02 23:05:07 +08:00
0b3feb0d5f feat: use Input.OTP to input totp code (#3922) 2025-07-02 18:22:59 +08:00
568c0e2c3d feat: show Organization.PasswordOptions in login UI (#3913) 2025-06-28 22:13:00 +08:00
f4ad2b4034 feat: remove "@" from name's forbidden chars 2025-06-27 18:41:50 +08:00
80 changed files with 710 additions and 284 deletions

View File

@ -66,7 +66,11 @@ func GetConfigBool(key string) bool {
func GetConfigInt64(key string) (int64, error) { func GetConfigInt64(key string) (int64, error) {
value := GetConfigString(key) value := GetConfigString(key)
num, err := strconv.ParseInt(value, 10, 64) num, err := strconv.ParseInt(value, 10, 64)
return num, err if err != nil {
return 0, fmt.Errorf("GetConfigInt64(%s) error, %s", key, err.Error())
}
return num, nil
} }
func GetConfigDataSourceName() string { func GetConfigDataSourceName() string {

View File

@ -42,6 +42,7 @@ type Response struct {
Name string `json:"name"` Name string `json:"name"`
Data interface{} `json:"data"` Data interface{} `json:"data"`
Data2 interface{} `json:"data2"` Data2 interface{} `json:"data2"`
Data3 interface{} `json:"data3"`
} }
type Captcha struct { type Captcha struct {

View File

@ -132,7 +132,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
if form.Type == ResponseTypeLogin { if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId) c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId) util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword} resp = &Response{Status: "ok", Msg: "", Data: userId, Data3: user.NeedUpdatePassword}
} else if form.Type == ResponseTypeCode { } else if form.Type == ResponseTypeCode {
clientId := c.Input().Get("clientId") clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType") responseType := c.Input().Get("responseType")
@ -154,7 +154,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
} }
resp = codeToResponse(code) resp = codeToResponse(code)
resp.Data2 = user.NeedUpdatePassword resp.Data3 = user.NeedUpdatePassword
if application.EnableSigninSession || application.HasPromptPage() { if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in // The prompt page needs the user to be signed in
c.SetSessionUsername(userId) c.SetSessionUsername(userId)
@ -168,7 +168,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host) token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
resp = tokenToResponse(token) resp = tokenToResponse(token)
resp.Data2 = user.NeedUpdatePassword resp.Data3 = user.NeedUpdatePassword
} }
} else if form.Type == ResponseTypeDevice { } else if form.Type == ResponseTypeDevice {
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode) authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
@ -195,14 +195,14 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast) object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast)
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword} resp = &Response{Status: "ok", Msg: "", Data: userId, Data3: user.NeedUpdatePassword}
} else if form.Type == ResponseTypeSaml { // saml flow } else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host) res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
} }
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: map[string]interface{}{"redirectUrl": redirectUrl, "method": method, "needUpdatePassword": user.NeedUpdatePassword}} resp = &Response{Status: "ok", Msg: "", Data: res, Data2: map[string]interface{}{"redirectUrl": redirectUrl, "method": method}, Data3: user.NeedUpdatePassword}
if application.EnableSigninSession || application.HasPromptPage() { if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in // The prompt page needs the user to be signed in
@ -355,20 +355,27 @@ func isProxyProviderType(providerType string) bool {
func checkMfaEnable(c *ApiController, user *object.User, organization *object.Organization, verificationType string) bool { func checkMfaEnable(c *ApiController, user *object.User, organization *object.Organization, verificationType string) bool {
if object.IsNeedPromptMfa(organization, user) { if object.IsNeedPromptMfa(organization, user) {
// The prompt page needs the user to be srigned in // The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId()) c.SetSessionUsername(user.GetId())
c.ResponseOk(object.RequiredMfa) c.ResponseOk(object.RequiredMfa)
return true return true
} }
if user.IsMfaEnabled() { if user.IsMfaEnabled() {
currentTime := util.String2Time(util.GetCurrentTime())
mfaRememberDeadline := util.String2Time(user.MfaRememberDeadline)
if user.MfaRememberDeadline != "" && mfaRememberDeadline.After(currentTime) {
return false
}
c.setMfaUserSession(user.GetId()) c.setMfaUserSession(user.GetId())
mfaList := object.GetAllMfaProps(user, true) mfaList := object.GetAllMfaProps(user, true)
mfaAllowList := []*object.MfaProps{} mfaAllowList := []*object.MfaProps{}
mfaRememberInHours := organization.MfaRememberInHours
for _, prop := range mfaList { for _, prop := range mfaList {
if prop.MfaType == verificationType || !prop.Enabled { if prop.MfaType == verificationType || !prop.Enabled {
continue continue
} }
prop.MfaRememberInHours = mfaRememberInHours
mfaAllowList = append(mfaAllowList, prop) mfaAllowList = append(mfaAllowList, prop)
} }
if len(mfaAllowList) >= 1 { if len(mfaAllowList) >= 1 {
@ -555,8 +562,11 @@ func (c *ApiController) Login() {
c.ResponseError(c.T("auth:The login method: login with LDAP is not enabled for the application")) c.ResponseError(c.T("auth:The login method: login with LDAP is not enabled for the application"))
return return
} }
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
var enableCaptcha bool var enableCaptcha bool
if enableCaptcha, err = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username); err != nil { if enableCaptcha, err = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username, clientIp); err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} else if enableCaptcha { } else if enableCaptcha {
@ -970,6 +980,28 @@ func (c *ApiController) Login() {
return return
} }
var application *object.Application
if authForm.ClientId == "" {
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
} else {
application, err = object.GetApplicationByClientId(authForm.ClientId)
}
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
var organization *object.Organization
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
if err != nil {
c.ResponseError(c.T(err.Error()))
}
if authForm.Passcode != "" { if authForm.Passcode != "" {
if authForm.MfaType == c.GetSession("verificationCodeType") { if authForm.MfaType == c.GetSession("verificationCodeType") {
c.ResponseError("Invalid multi-factor authentication type") c.ResponseError("Invalid multi-factor authentication type")
@ -996,6 +1028,17 @@ func (c *ApiController) Login() {
} }
} }
if authForm.EnableMfaRemember {
mfaRememberInSeconds := organization.MfaRememberInHours * 3600
currentTime := util.String2Time(util.GetCurrentTime())
duration := time.Duration(mfaRememberInSeconds) * time.Second
user.MfaRememberDeadline = util.Time2String(currentTime.Add(duration))
_, err = object.UpdateUser(user.GetId(), user, []string{"mfa_remember_deadline"}, user.IsAdmin)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.SetSession("verificationCodeType", "") c.SetSession("verificationCodeType", "")
} else if authForm.RecoveryCode != "" { } else if authForm.RecoveryCode != "" {
err = object.MfaRecover(user, authForm.RecoveryCode) err = object.MfaRecover(user, authForm.RecoveryCode)
@ -1008,22 +1051,6 @@ func (c *ApiController) Login() {
return return
} }
var application *object.Application
if authForm.ClientId == "" {
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
} else {
application, err = object.GetApplicationByClientId(authForm.ClientId)
}
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
resp = c.HandleLoggedIn(application, user, &authForm) resp = c.HandleLoggedIn(application, user, &authForm)
c.setMfaUserSession("") c.setMfaUserSession("")
@ -1222,27 +1249,26 @@ func (c *ApiController) GetQRCode() {
func (c *ApiController) GetCaptchaStatus() { func (c *ApiController) GetCaptchaStatus() {
organization := c.Input().Get("organization") organization := c.Input().Get("organization")
userId := c.Input().Get("userId") userId := c.Input().Get("userId")
user, err := object.GetUserByFields(organization, userId) applicationName := c.Input().Get("application")
application, err := object.GetApplication(fmt.Sprintf("admin/%s", applicationName))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if application == nil {
c.ResponseError("application not found")
return
}
captchaEnabled := false clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
if user != nil { captchaEnabled, err := object.CheckToEnableCaptcha(application, organization, userId, clientIp)
var failedSigninLimit int
failedSigninLimit, _, err = object.GetFailedSigninConfigByUser(user)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if user.SigninWrongTimes >= failedSigninLimit {
captchaEnabled = true
}
}
c.ResponseOk(captchaEnabled) c.ResponseOk(captchaEnabled)
return
} }
// Callback // Callback

View File

@ -58,6 +58,12 @@ func (c *ApiController) MfaSetupInitiate() {
return return
} }
organization, err := object.GetOrganizationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
mfaProps, err := MfaUtil.Initiate(user.GetId()) mfaProps, err := MfaUtil.Initiate(user.GetId())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
@ -66,6 +72,7 @@ func (c *ApiController) MfaSetupInitiate() {
recoveryCode := uuid.NewString() recoveryCode := uuid.NewString()
mfaProps.RecoveryCodes = []string{recoveryCode} mfaProps.RecoveryCodes = []string{recoveryCode}
mfaProps.MfaRememberInHours = organization.MfaRememberInHours
resp := mfaProps resp := mfaProps
c.ResponseOk(resp) c.ResponseOk(resp)

View File

@ -98,6 +98,10 @@ func (c *ApiController) GetOrganization() {
return return
} }
if organization != nil && organization.MfaRememberInHours == 0 {
organization.MfaRememberInHours = 12
}
c.ResponseOk(organization) c.ResponseOk(organization)
} }

View File

@ -140,6 +140,9 @@ func (c *ApiController) SendEmail() {
} }
content = strings.Replace(content, "%{user.friendlyName}", userString, 1) content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
matchContent := object.ResetLinkReg.Find([]byte(content))
content = strings.Replace(content, string(matchContent), "", -1)
for _, receiver := range emailForm.Receivers { for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender) err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
if err != nil { if err != nil {

View File

@ -16,6 +16,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"time" "time"
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
@ -460,7 +461,18 @@ func (c *ApiController) IntrospectToken() {
} }
if token != nil { if token != nil {
application, err = object.GetApplication(fmt.Sprintf("%s/%s", token.Owner, token.Application))
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), token.Application))
return
}
introspectionResponse.TokenType = token.TokenType introspectionResponse.TokenType = token.TokenType
introspectionResponse.ClientId = application.ClientId
} }
c.Data["json"] = introspectionResponse c.Data["json"] = introspectionResponse

View File

@ -574,7 +574,7 @@ func (c *ApiController) SetPassword() {
targetUser.LastChangePasswordTime = util.GetCurrentTime() targetUser.LastChangePasswordTime = util.GetCurrentTime()
if user.Ldap == "" { if user.Ldap == "" {
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false) _, err = object.UpdateUser(userId, targetUser, []string{"password", "password_salt", "need_update_password", "password_type", "last_change_password_time"}, false)
} else { } else {
if isAdmin { if isAdmin {
err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage()) err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage())

View File

@ -258,7 +258,7 @@ func (c *ApiController) SendVerificationCode() {
return return
} }
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest) sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest, vform.Method, c.Ctx.Request.Host, application.Name)
case object.VerifyTypePhone: case object.VerifyTypePhone:
if vform.Method == LoginVerification || vform.Method == ForgetVerification { if vform.Method == LoginVerification || vform.Method == ForgetVerification {
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest { if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {

View File

@ -23,7 +23,7 @@ func NewArgon2idCredManager() *Argon2idCredManager {
return cm return cm
} }
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *Argon2idCredManager) GetHashedPassword(password string, salt string) string {
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams) hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil { if err != nil {
return "" return ""
@ -31,7 +31,7 @@ func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt strin
return hash return hash
} }
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd) match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
return match return match
} }

View File

@ -9,7 +9,7 @@ func NewBcryptCredManager() *BcryptCredManager {
return cm return cm
} }
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *BcryptCredManager) GetHashedPassword(password string, salt string) string {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return "" return ""
@ -17,7 +17,7 @@ func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string,
return string(bytes) return string(bytes)
} }
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd)) err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
return err == nil return err == nil
} }

View File

@ -15,8 +15,8 @@
package cred package cred
type CredManager interface { type CredManager interface {
GetHashedPassword(password string, userSalt string, organizationSalt string) string GetHashedPassword(password string, salt string) string
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool IsPasswordCorrect(password string, passwordHash string, salt string) bool
} }
func GetCredManager(passwordType string) CredManager { func GetCredManager(passwordType string) CredManager {

View File

@ -37,14 +37,10 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
return cm return cm
} }
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, salt string) string {
res := getMd5HexDigest(password) return getMd5HexDigest(getMd5HexDigest(password) + salt)
if userSalt != "" {
res = getMd5HexDigest(res + userSalt)
}
return res
} }
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt) return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
} }

View File

@ -28,13 +28,13 @@ func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
return cm return cm
} }
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, salt string) string {
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised // https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt) decodedSalt, _ := base64.StdEncoding.DecodeString(salt)
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New) res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
return base64.StdEncoding.EncodeToString(res) return base64.StdEncoding.EncodeToString(res)
} }
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt) return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
} }

View File

@ -32,12 +32,8 @@ func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
return cm return cm
} }
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, salt string) string {
iterations := 260000 iterations := 260000
salt := userSalt
if salt == "" {
salt = organizationSalt
}
saltBytes := []byte(salt) saltBytes := []byte(salt)
passwordBytes := []byte(password) passwordBytes := []byte(password)
@ -46,7 +42,7 @@ func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt st
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64 return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
} }
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool { func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, _salt string) bool {
parts := strings.Split(passwordHash, "$") parts := strings.Split(passwordHash, "$")
if len(parts) != 4 { if len(parts) != 4 {
return false return false

View File

@ -21,10 +21,10 @@ func NewPlainCredManager() *PlainCredManager {
return cm return cm
} }
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *PlainCredManager) GetHashedPassword(password string, salt string) string {
return password return password
} }
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == plainPwd return hashedPwd == plainPwd
} }

View File

@ -37,14 +37,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
return cm return cm
} }
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *Sha256SaltCredManager) GetHashedPassword(password string, salt string) string {
res := getSha256HexDigest(password) return getSha256HexDigest(getSha256HexDigest(password) + salt)
if organizationSalt != "" {
res = getSha256HexDigest(res + organizationSalt)
}
return res
} }
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt) return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
} }

View File

@ -23,12 +23,12 @@ func TestGetSaltedPassword(t *testing.T) {
password := "123456" password := "123456"
salt := "123" salt := "123"
cm := NewSha256SaltCredManager() cm := NewSha256SaltCredManager()
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt)) fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, salt))
} }
func TestGetPassword(t *testing.T) { func TestGetPassword(t *testing.T) {
password := "123456" password := "123456"
cm := NewSha256SaltCredManager() cm := NewSha256SaltCredManager()
// https://passwordsgenerator.net/sha256-hash-generator/ // https://passwordsgenerator.net/sha256-hash-generator/
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", "")) fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, ""))
} }

View File

@ -37,14 +37,10 @@ func NewSha512SaltCredManager() *Sha512SaltCredManager {
return cm return cm
} }
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string { func (cm *Sha512SaltCredManager) GetHashedPassword(password string, salt string) string {
res := getSha512HexDigest(password) return getSha512HexDigest(getSha512HexDigest(password) + salt)
if organizationSalt != "" {
res = getSha512HexDigest(res + organizationSalt)
}
return res
} }
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool { func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt) return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
} }

View File

@ -64,6 +64,7 @@ type AuthForm struct {
MfaType string `json:"mfaType"` MfaType string `json:"mfaType"`
Passcode string `json:"passcode"` Passcode string `json:"passcode"`
RecoveryCode string `json:"recoveryCode"` RecoveryCode string `json:"recoveryCode"`
EnableMfaRemember bool `json:"enableMfaRemember"`
Plan string `json:"plan"` Plan string `json:"plan"`
Pricing string `json:"pricing"` Pricing string `json:"pricing"`

View File

@ -190,7 +190,7 @@ func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
userInfo := UserInfo{ userInfo := UserInfo{
Id: douyinUserInfo.Data.OpenId, Id: douyinUserInfo.Data.OpenId,
Username: douyinUserInfo.Data.Nickname, Username: douyinUserInfo.Data.OpenId,
DisplayName: douyinUserInfo.Data.Nickname, DisplayName: douyinUserInfo.Data.Nickname,
AvatarUrl: douyinUserInfo.Data.Avatar, AvatarUrl: douyinUserInfo.Data.Avatar,
} }

View File

@ -220,10 +220,15 @@ func checkSigninErrorTimes(user *User, lang string) error {
} }
func CheckPassword(user *User, password string, lang string, options ...bool) error { func CheckPassword(user *User, password string, lang string, options ...bool) error {
if password == "" {
return fmt.Errorf(i18n.Translate(lang, "check:Password cannot be empty"))
}
enableCaptcha := false enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
// check the login error times // check the login error times
if !enableCaptcha { if !enableCaptcha {
err := checkSigninErrorTimes(user, lang) err := checkSigninErrorTimes(user, lang)
@ -236,35 +241,41 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
if err != nil { if err != nil {
return err return err
} }
if organization == nil { if organization == nil {
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist")) return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
} }
if password == "" {
return fmt.Errorf(i18n.Translate(lang, "check:Password cannot be empty"))
}
passwordType := user.PasswordType passwordType := user.PasswordType
if passwordType == "" { if passwordType == "" {
passwordType = organization.PasswordType passwordType = organization.PasswordType
} }
credManager := cred.GetCredManager(passwordType) credManager := cred.GetCredManager(passwordType)
if credManager != nil { if credManager == nil {
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), passwordType)
}
if organization.MasterPassword != "" { if organization.MasterPassword != "" {
if password == organization.MasterPassword || credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) { if password == organization.MasterPassword || credManager.IsPasswordCorrect(password, organization.MasterPassword, organization.PasswordSalt) {
return resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
} }
} }
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) { if !credManager.IsPasswordCorrect(password, user.Password, organization.PasswordSalt) && !credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt) {
return resetUserSigninErrorTimes(user)
}
return recordSigninErrorInfo(user, lang, enableCaptcha) return recordSigninErrorInfo(user, lang, enableCaptcha)
} else {
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
} }
isOutdated := passwordType != organization.PasswordType
if isOutdated {
user.Password = password
user.UpdateUserPassword(organization)
_, err = UpdateUser(user.GetId(), user, []string{"password", "password_type", "password_salt"}, true)
if err != nil {
return err
}
}
return resetUserSigninErrorTimes(user)
} }
func CheckPasswordComplexityByOrg(organization *Organization, password string) string { func CheckPasswordComplexityByOrg(organization *Organization, password string) string {
@ -593,31 +604,41 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
return "" return ""
} }
func CheckToEnableCaptcha(application *Application, organization, username string) (bool, error) { func CheckToEnableCaptcha(application *Application, organization, username string, clientIp string) (bool, error) {
if len(application.Providers) == 0 { if len(application.Providers) == 0 {
return false, nil return false, nil
} }
for _, providerItem := range application.Providers { for _, providerItem := range application.Providers {
if providerItem.Provider == nil { if providerItem.Provider == nil || providerItem.Provider.Category != "Captcha" {
continue continue
} }
if providerItem.Provider.Category == "Captcha" {
if providerItem.Rule == "Internet-Only" {
if util.IsInternetIp(clientIp) {
return true, nil
}
}
if providerItem.Rule == "Dynamic" { if providerItem.Rule == "Dynamic" {
user, err := GetUserByFields(organization, username) user, err := GetUserByFields(organization, username)
if err != nil { if err != nil {
return false, err return false, err
} }
failedSigninLimit := application.FailedSigninLimit if user != nil {
if failedSigninLimit == 0 { failedSigninLimit, _, err := GetFailedSigninConfigByUser(user)
failedSigninLimit = DefaultFailedSigninLimit if err != nil {
return false, err
} }
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil return user.SigninWrongTimes >= failedSigninLimit, nil
} }
return providerItem.Rule == "Always", nil
} return false, nil
}
return providerItem.Rule == "Always", nil
} }
return false, nil return false, nil

View File

@ -103,7 +103,7 @@ func GetDashboard(owner string) (*map[string][]int64, error) {
func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 { func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 {
count := dashboardMapItem.itemCount count := dashboardMapItem.itemCount
for _, e := range dashboardMapItem.dashboardDateItems { for _, e := range dashboardMapItem.dashboardDateItems {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime) createdTime, _ := time.Parse(time.RFC3339, e.CreatedTime)
if createdTime.Before(before) { if createdTime.Before(before) {
count++ count++
} }

View File

@ -20,6 +20,7 @@ package object
import "testing" import "testing"
func TestDumpToFile(t *testing.T) { func TestDumpToFile(t *testing.T) {
createDatabase = false
InitConfig() InitConfig()
err := DumpToFile("./init_data_dump.json") err := DumpToFile("./init_data_dump.json")

View File

@ -28,6 +28,7 @@ type MfaProps struct {
CountryCode string `json:"countryCode,omitempty"` CountryCode string `json:"countryCode,omitempty"`
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
RecoveryCodes []string `json:"recoveryCodes,omitempty"` RecoveryCodes []string `json:"recoveryCodes,omitempty"`
MfaRememberInHours int `json:"mfaRememberInHours"`
} }
type MfaInterface interface { type MfaInterface interface {

View File

@ -85,6 +85,7 @@ type Organization struct {
WidgetItems []string `xorm:"varchar(1000)" json:"widgetItems"` WidgetItems []string `xorm:"varchar(1000)" json:"widgetItems"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"` MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
MfaRememberInHours int `json:"mfaRememberInHours"`
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"` AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
} }
@ -222,7 +223,7 @@ func UpdateOrganization(id string, organization *Organization, isGlobalAdmin boo
if organization.MasterPassword != "" && organization.MasterPassword != "***" { if organization.MasterPassword != "" && organization.MasterPassword != "***" {
credManager := cred.GetCredManager(organization.PasswordType) credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil { if credManager != nil {
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt) hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, organization.PasswordSalt)
organization.MasterPassword = hashedPassword organization.MasterPassword = hashedPassword
} }
} }
@ -536,7 +537,13 @@ func IsNeedPromptMfa(org *Organization, user *User) bool {
if org == nil || user == nil { if org == nil || user == nil {
return false return false
} }
for _, item := range org.MfaItems {
mfaItems := org.MfaItems
if len(user.MfaItems) > 0 {
mfaItems = user.MfaItems
}
for _, item := range mfaItems {
if item.Rule == "Required" { if item.Rule == "Required" {
if item.Name == EmailType && !user.MfaEmailEnabled { if item.Name == EmailType && !user.MfaEmailEnabled {
return true return true

View File

@ -49,17 +49,21 @@ func (plan *Plan) GetId() string {
return fmt.Sprintf("%s/%s", plan.Owner, plan.Name) return fmt.Sprintf("%s/%s", plan.Owner, plan.Name)
} }
func GetDuration(period string) (startTime time.Time, endTime time.Time) { func getDuration(period string) (string, string, error) {
startTime := time.Now()
var endTime time.Time
if period == PeriodYearly { if period == PeriodYearly {
startTime = time.Now()
endTime = startTime.AddDate(1, 0, 0) endTime = startTime.AddDate(1, 0, 0)
} else if period == PeriodMonthly { } else if period == PeriodMonthly {
startTime = time.Now()
endTime = startTime.AddDate(0, 1, 0) endTime = startTime.AddDate(0, 1, 0)
} else { } else {
panic(fmt.Sprintf("invalid period: %s", period)) return "", "", fmt.Errorf("invalid period: %s", period)
} }
return
startTimeString := startTime.Format(time.RFC3339)
endTimeString := endTime.Format(time.RFC3339)
return startTimeString, endTimeString, nil
} }
func GetPlanCount(owner, field, value string) (int64, error) { func GetPlanCount(owner, field, value string) (int64, error) {

View File

@ -206,11 +206,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
if plan == nil { if plan == nil {
return nil, nil, fmt.Errorf("the plan: %s does not exist", planName) return nil, nil, fmt.Errorf("the plan: %s does not exist", planName)
} }
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
sub, err := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
if err != nil {
return nil, nil, err
}
_, err = AddSubscription(sub) _, err = AddSubscription(sub)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name) returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
} }
} }

View File

@ -48,8 +48,8 @@ type Subscription struct {
Plan string `xorm:"varchar(100)" json:"plan"` Plan string `xorm:"varchar(100)" json:"plan"`
Payment string `xorm:"varchar(100)" json:"payment"` Payment string `xorm:"varchar(100)" json:"payment"`
StartTime time.Time `json:"startTime"` StartTime string `xorm:"varchar(100)" json:"startTime"`
EndTime time.Time `json:"endTime"` EndTime string `xorm:"varchar(100)" json:"endTime"`
Period string `xorm:"varchar(100)" json:"period"` Period string `xorm:"varchar(100)" json:"period"`
State SubscriptionState `xorm:"varchar(100)" json:"state"` State SubscriptionState `xorm:"varchar(100)" json:"state"`
} }
@ -84,9 +84,19 @@ func (sub *Subscription) UpdateState() error {
} }
if sub.State == SubStateActive || sub.State == SubStateUpcoming || sub.State == SubStateExpired { if sub.State == SubStateActive || sub.State == SubStateUpcoming || sub.State == SubStateExpired {
if sub.EndTime.Before(time.Now()) { startTime, err := time.Parse(time.RFC3339, sub.StartTime)
if err != nil {
return err
}
endTime, err := time.Parse(time.RFC3339, sub.EndTime)
if err != nil {
return err
}
if endTime.Before(time.Now()) {
sub.State = SubStateExpired sub.State = SubStateExpired
} else if sub.StartTime.After(time.Now()) { } else if startTime.After(time.Now()) {
sub.State = SubStateUpcoming sub.State = SubStateUpcoming
} else { } else {
sub.State = SubStateActive sub.State = SubStateActive
@ -103,10 +113,15 @@ func (sub *Subscription) UpdateState() error {
return nil return nil
} }
func NewSubscription(owner, userName, planName, paymentName, period string) *Subscription { func NewSubscription(owner, userName, planName, paymentName, period string) (*Subscription, error) {
startTime, endTime := GetDuration(period) startTime, endTime, err := getDuration(period)
if err != nil {
return nil, err
}
id := util.GenerateId()[:6] id := util.GenerateId()[:6]
return &Subscription{
res := &Subscription{
Owner: owner, Owner: owner,
Name: "sub_" + id, Name: "sub_" + id,
DisplayName: "New Subscription - " + id, DisplayName: "New Subscription - " + id,
@ -121,6 +136,7 @@ func NewSubscription(owner, userName, planName, paymentName, period string) *Sub
Period: period, Period: period,
State: SubStatePending, // waiting for payment complete State: SubStatePending, // waiting for payment complete
} }
return res, nil
} }
func GetSubscriptionCount(owner, field, value string) (int64, error) { func GetSubscriptionCount(owner, field, value string) (int64, error) {

View File

@ -212,6 +212,8 @@ type User struct {
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"` ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"` MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
MfaRememberDeadline string `xorm:"varchar(100)" json:"mfaRememberDeadline"`
NeedUpdatePassword bool `json:"needUpdatePassword"` NeedUpdatePassword bool `json:"needUpdatePassword"`
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"` IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
} }
@ -791,11 +793,11 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup", "eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud", "microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo", "spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist", "yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist", "mfa_items", "mfa_remember_deadline",
} }
} }
if isAdmin { if isAdmin {
columns = append(columns, "name", "id", "email", "phone", "country_code", "type", "balance") columns = append(columns, "name", "id", "email", "phone", "country_code", "type", "balance", "mfa_items")
} }
columns = append(columns, "updated_time") columns = append(columns, "updated_time")

View File

@ -42,8 +42,9 @@ func (user *User) UpdateUserHash() error {
func (user *User) UpdateUserPassword(organization *Organization) { func (user *User) UpdateUserPassword(organization *Organization) {
credManager := cred.GetCredManager(organization.PasswordType) credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil { if credManager != nil {
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt) hashedPassword := credManager.GetHashedPassword(user.Password, organization.PasswordSalt)
user.Password = hashedPassword user.Password = hashedPassword
user.PasswordType = organization.PasswordType user.PasswordType = organization.PasswordType
user.PasswordSalt = organization.PasswordSalt
} }
} }

View File

@ -19,6 +19,8 @@ import (
"fmt" "fmt"
"math" "math"
"math/rand" "math/rand"
"net/url"
"regexp"
"strings" "strings"
"time" "time"
@ -33,6 +35,8 @@ type VerifyResult struct {
Msg string Msg string
} }
var ResetLinkReg *regexp.Regexp
const ( const (
VerificationSuccess = iota VerificationSuccess = iota
wrongCodeError wrongCodeError
@ -45,6 +49,10 @@ const (
VerifyTypeEmail = "email" VerifyTypeEmail = "email"
) )
func init() {
ResetLinkReg = regexp.MustCompile("(?s)<reset-link>(.*?)</reset-link>")
}
type VerificationRecord struct { type VerificationRecord struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -81,7 +89,7 @@ func IsAllowSend(user *User, remoteAddr, recordType string) error {
return nil return nil
} }
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error { func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string, method string, host string, applicationName string) error {
sender := organization.DisplayName sender := organization.DisplayName
title := provider.Title title := provider.Title
@ -93,6 +101,23 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes." // "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 method == "forget" {
originFrontend, _ := getOriginFromHost(host)
query := url.Values{}
query.Add("code", code)
query.Add("username", user.Name)
query.Add("dest", util.GetMaskedEmail(dest))
forgetURL := originFrontend + "/forget/" + applicationName + "?" + query.Encode()
content = strings.Replace(content, "%link", forgetURL, -1)
content = strings.Replace(content, "<reset-link>", "", -1)
content = strings.Replace(content, "</reset-link>", "", -1)
} else {
matchContent := ResetLinkReg.Find([]byte(content))
content = strings.Replace(content, string(matchContent), "", -1)
}
userString := "Hi" userString := "Hi"
if user != nil { if user != nil {
userString = user.GetFriendlyName() userString = user.GetFriendlyName()

View File

@ -66,6 +66,10 @@ func AutoSigninFilter(ctx *context.Context) {
responseError(ctx, err.Error()) responseError(ctx, err.Error())
return return
} }
if application == nil {
responseError(ctx, fmt.Sprintf("No application is found for userId: app/%s", token.Application))
return
}
setSessionUser(ctx, userId) setSessionUser(ctx, userId)
setSessionOidc(ctx, token.Scope, application.ClientId) setSessionOidc(ctx, token.Scope, application.ClientId)

View File

@ -185,17 +185,3 @@ func removePort(s string) string {
} }
return ipStr return ipStr
} }
func isHostIntranet(s string) bool {
ipStr, _, err := net.SplitHostPort(s)
if err != nil {
ipStr = s
}
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
}

View File

@ -83,7 +83,7 @@ func CorsFilter(ctx *context.Context) {
setCorsHeaders(ctx, origin) setCorsHeaders(ctx, origin)
} else if originHostname == host { } else if originHostname == host {
setCorsHeaders(ctx, origin) setCorsHeaders(ctx, origin)
} else if isHostIntranet(host) { } else if util.IsHostIntranet(host) {
setCorsHeaders(ctx, origin) setCorsHeaders(ctx, origin)
} else { } else {
ok, err := object.IsOriginAllowed(origin) ok, err := object.IsOriginAllowed(origin)

View File

@ -23,7 +23,7 @@ import (
"github.com/beego/beego/context" "github.com/beego/beego/context"
) )
var forbiddenChars = `/?:@#&%=+;` var forbiddenChars = `/?:#&%=+;`
func FieldValidationFilter(ctx *context.Context) { func FieldValidationFilter(ctx *context.Context) {
if ctx.Input.Method() != "POST" { if ctx.Input.Method() != "POST" {

47
util/network.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"net"
)
func IsInternetIp(ip string) bool {
ipStr, _, err := net.SplitHostPort(ip)
if err != nil {
ipStr = ip
}
parsedIP := net.ParseIP(ipStr)
if parsedIP == nil {
return false
}
return !parsedIP.IsPrivate() && !parsedIP.IsLoopback() && !parsedIP.IsMulticast() && !parsedIP.IsUnspecified()
}
func IsHostIntranet(ip string) bool {
ipStr, _, err := net.SplitHostPort(ip)
if err != nil {
ipStr = ip
}
parsedIP := net.ParseIP(ipStr)
if parsedIP == nil {
return false
}
return parsedIP.IsPrivate() || parsedIP.IsLoopback() || parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast()
}

View File

@ -603,6 +603,16 @@ class OrganizationEditPage extends React.Component {
/> />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:MFA remember time"), i18next.t("application:MFA remember time - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber style={{width: "150px"}} value={this.state.organization.mfaRememberInHours} min={1} step={1} precision={0} addonAfter="Hours" onChange={value => {
this.updateOrganizationField("mfaRememberInHours", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} : {Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} :

View File

@ -25,6 +25,7 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class OrganizationListPage extends BaseListPage { class OrganizationListPage extends BaseListPage {
newOrganization() { newOrganization() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const DefaultMfaRememberInHours = 12;
return { return {
owner: "admin", // this.props.account.organizationname, owner: "admin", // this.props.account.organizationname,
name: `organization_${randomName}`, name: `organization_${randomName}`,
@ -48,6 +49,7 @@ class OrganizationListPage extends BaseListPage {
enableSoftDeletion: false, enableSoftDeletion: false,
isProfilePublic: true, isProfilePublic: true,
enableTour: true, enableTour: true,
mfaRememberInHours: DefaultMfaRememberInHours,
accountItems: [ accountItems: [
{name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"}, {name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},

View File

@ -1227,7 +1227,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.")} > <Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes. <reset-link>Or click %link to reset</reset-link>")} >
{i18next.t("provider:Reset to Default Text")} {i18next.t("provider:Reset to Default Text")}
</Button> </Button>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} > <Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >

View File

@ -696,18 +696,27 @@ export const MfaRulePrompted = "Prompted";
export const MfaRuleOptional = "Optional"; export const MfaRuleOptional = "Optional";
export function isRequiredEnableMfa(user, organization) { export function isRequiredEnableMfa(user, organization) {
if (!user || !organization || !organization.mfaItems) { if (!user || !organization || (!organization.mfaItems && !user.mfaItems)) {
return false; return false;
} }
return getMfaItemsByRules(user, organization, [MfaRuleRequired]).length > 0; return getMfaItemsByRules(user, organization, [MfaRuleRequired]).length > 0;
} }
export function getMfaItemsByRules(user, organization, mfaRules = []) { export function getMfaItemsByRules(user, organization, mfaRules = []) {
if (!user || !organization || !organization.mfaItems) { if (!user || !organization || (!organization.mfaItems && !user.mfaItems)) {
return []; return [];
} }
return organization.mfaItems.filter((mfaItem) => mfaRules.includes(mfaItem.rule)) let mfaItems = organization.mfaItems;
if (user.mfaItems && user.mfaItems.length !== 0) {
mfaItems = user.mfaItems;
}
if (mfaItems === null) {
return [];
}
return mfaItems.filter((mfaItem) => mfaRules.includes(mfaItem.rule))
.filter((mfaItem) => user.multiFactorAuths.some((mfa) => mfa.mfaType === mfaItem.name && !mfa.enabled)); .filter((mfaItem) => user.multiFactorAuths.some((mfa) => mfa.mfaType === mfaItem.name && !mfa.enabled));
} }
@ -1571,6 +1580,11 @@ export function getDefaultHtmlEmailContent() {
<div class="code"> <div class="code">
%s %s
</div> </div>
<reset-link>
<div class="link">
Or click this <a href="%link">link</a> to reset
</div>
</reset-link>
<p>Thanks</p> <p>Thanks</p>
<p>Casbin Team</p> <p>Casbin Team</p>
<hr> <hr>

View File

@ -42,6 +42,7 @@ import * as MfaBackend from "./backend/MfaBackend";
import AccountAvatar from "./account/AccountAvatar"; import AccountAvatar from "./account/AccountAvatar";
import FaceIdTable from "./table/FaceIdTable"; import FaceIdTable from "./table/FaceIdTable";
import MfaAccountTable from "./table/MfaAccountTable"; import MfaAccountTable from "./table/MfaAccountTable";
import MfaTable from "./table/MfaTable";
const {Option} = Select; const {Option} = Select;
@ -926,6 +927,19 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
); );
} else if (accountItem.name === "MFA items") {
return (<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} :
</Col>
<Col span={22} >
<MfaTable
title={i18next.t("general:MFA items")}
table={this.state.user.mfaItems ?? []}
onUpdateTable={(value) => {this.updateUserField("mfaItems", value);}}
/>
</Col>
</Row>);
} else if (accountItem.name === "Multi-factor authentication") { } else if (accountItem.name === "Multi-factor authentication") {
return ( return (
!this.isSelfOrAdmin() ? null : ( !this.isSelfOrAdmin() ? null : (

View File

@ -163,7 +163,7 @@ export function getWechatQRCode(providerId) {
} }
export function getCaptchaStatus(values) { export function getCaptchaStatus(values) {
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&userId=${values["username"]}`, { return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&userId=${values["username"]}&application=${values["application"]}`, {
method: "GET", method: "GET",
credentials: "include", credentials: "include",
headers: { headers: {

View File

@ -166,7 +166,7 @@ class AuthCallback extends React.Component {
const responseType = this.getResponseType(); const responseType = this.getResponseType();
const handleLogin = (res) => { const handleLogin = (res) => {
if (responseType === "login") { if (responseType === "login") {
if (res.data2) { if (res.data3) {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
Setting.goToLinkSoft(this, `/forget/${applicationName}`); Setting.goToLinkSoft(this, `/forget/${applicationName}`);
return; return;
@ -176,7 +176,7 @@ class AuthCallback extends React.Component {
const link = Setting.getFromLink(); const link = Setting.getFromLink();
Setting.goToLink(link); Setting.goToLink(link);
} else if (responseType === "code") { } else if (responseType === "code") {
if (res.data2) { if (res.data3) {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
Setting.goToLinkSoft(this, `/forget/${applicationName}`); Setting.goToLinkSoft(this, `/forget/${applicationName}`);
return; return;
@ -185,7 +185,7 @@ class AuthCallback extends React.Component {
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`); Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
// Setting.showMessage("success", `Authorization code: ${res.data}`); // Setting.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token") { } else if (responseType === "token" || responseType === "id_token") {
if (res.data2) { if (res.data3) {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
Setting.goToLinkSoft(this, `/forget/${applicationName}`); Setting.goToLinkSoft(this, `/forget/${applicationName}`);
return; return;
@ -207,7 +207,7 @@ class AuthCallback extends React.Component {
relayState: oAuthParams.relayState, relayState: oAuthParams.relayState,
}); });
} else { } else {
if (res.data2.needUpdatePassword) { if (res.data3) {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
Setting.goToLinkSoft(this, `/forget/${applicationName}`); Setting.goToLinkSoft(this, `/forget/${applicationName}`);
return; return;

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Form, Input, Row, Select, Steps} from "antd"; import {Button, Col, Form, Input, Popover, Row, Select, Steps} from "antd";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Util from "./Util"; import * as Util from "./Util";
@ -31,18 +31,21 @@ const {Option} = Select;
class ForgetPage extends React.Component { class ForgetPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const queryParams = new URLSearchParams(location.search);
this.state = { this.state = {
classes: props, classes: props,
applicationName: props.applicationName ?? props.match.params?.applicationName, applicationName: props.applicationName ?? props.match.params?.applicationName,
msg: null, msg: null,
name: props.account ? props.account.name : "", name: props.account ? props.account.name : queryParams.get("username"),
username: props.account ? props.account.name : "", username: props.account ? props.account.name : "",
phone: "", phone: "",
email: "", email: "",
dest: "", dest: "",
isVerifyTypeFixed: false, isVerifyTypeFixed: false,
verifyType: "", // "email", "phone" verifyType: "", // "email", "phone"
current: 0, current: queryParams.get("code") ? 2 : 0,
code: queryParams.get("code"),
queryParams: queryParams,
}; };
this.form = React.createRef(); this.form = React.createRef();
} }
@ -148,9 +151,26 @@ class ForgetPage extends React.Component {
} }
} }
onFinish(values) { async onFinish(values) {
values.username = this.state.name; values.username = this.state.name;
values.userOwner = this.getApplicationObj()?.organizationObj.name; values.userOwner = this.getApplicationObj()?.organizationObj.name;
if (this.state.queryParams.get("code")) {
const res = await UserBackend.verifyCode({
application: this.getApplicationObj().name,
organization: values.userOwner,
username: this.state.queryParams.get("dest"),
name: this.state.name,
code: this.state.code,
type: "login",
});
if (res.status !== "ok") {
Setting.showMessage("error", res.msg);
return;
}
}
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword, this.state.code).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword, this.state.code).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
const linkInStorage = sessionStorage.getItem("signinUrl"); const linkInStorage = sessionStorage.getItem("signinUrl");
@ -385,6 +405,7 @@ class ForgetPage extends React.Component {
}, },
]} ]}
/> />
<Popover placement={window.innerWidth >= 960 ? "right" : "top"} content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
<Form.Item <Form.Item
name="newPassword" name="newPassword"
hidden={this.state.current !== 2} hidden={this.state.current !== 2}
@ -407,8 +428,25 @@ class ForgetPage extends React.Component {
<Input.Password <Input.Password
prefix={<LockOutlined />} prefix={<LockOutlined />}
placeholder={i18next.t("general:Password")} placeholder={i18next.t("general:Password")}
onChange={(e) => {
this.setState({
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value),
});
}}
onFocus={() => {
this.setState({
passwordPopoverOpen: application.organizationObj.passwordOptions?.length > 0,
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("newPassword") ?? ""),
});
}}
onBlur={() => {
this.setState({
passwordPopoverOpen: false,
});
}}
/> />
</Form.Item> </Form.Item>
</Popover>
<Form.Item <Form.Item
name="confirm" name="confirm"
dependencies={["newPassword"]} dependencies={["newPassword"]}

View File

@ -134,6 +134,8 @@ class LoginPage extends React.Component {
return CaptchaRule.Always; return CaptchaRule.Always;
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) { } else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
return CaptchaRule.Dynamic; return CaptchaRule.Dynamic;
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Internet-Only")) {
return CaptchaRule.InternetOnly;
} else { } else {
return CaptchaRule.Never; return CaptchaRule.Never;
} }
@ -443,6 +445,9 @@ class LoginPage extends React.Component {
} else if (captchaRule === CaptchaRule.Dynamic) { } else if (captchaRule === CaptchaRule.Dynamic) {
this.checkCaptchaStatus(values); this.checkCaptchaStatus(values);
return; return;
} else if (captchaRule === CaptchaRule.InternetOnly) {
this.checkCaptchaStatus(values);
return;
} }
} }
this.login(values); this.login(values);
@ -491,9 +496,9 @@ class LoginPage extends React.Component {
const responseType = values["type"]; const responseType = values["type"];
if (responseType === "login") { if (responseType === "login") {
if (res.data2) { if (res.data3) {
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search); sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
Setting.goToLink(this, `/forget/${this.state.applicationName}`); Setting.goToLinkSoft(this, `/forget/${this.state.applicationName}`);
} }
Setting.showMessage("success", i18next.t("application:Logged in successfully")); Setting.showMessage("success", i18next.t("application:Logged in successfully"));
this.props.onLoginSuccess(); this.props.onLoginSuccess();
@ -505,9 +510,9 @@ class LoginPage extends React.Component {
userCodeStatus: "success", userCodeStatus: "success",
}); });
} else if (responseType === "token" || responseType === "id_token") { } else if (responseType === "token" || responseType === "id_token") {
if (res.data2) { if (res.data3) {
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search); sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
Setting.goToLink(this, `/forget/${this.state.applicationName}`); Setting.goToLinkSoft(this, `/forget/${this.state.applicationName}`);
} }
const amendatoryResponseType = responseType === "token" ? "access_token" : responseType; const amendatoryResponseType = responseType === "token" ? "access_token" : responseType;
const accessToken = res.data; const accessToken = res.data;
@ -517,9 +522,9 @@ class LoginPage extends React.Component {
this.props.onLoginSuccess(window.location.href); this.props.onLoginSuccess(window.location.href);
return; return;
} }
if (res.data2.needUpdatePassword) { if (res.data3) {
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search); sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
Setting.goToLink(this, `/forget/${this.state.applicationName}`); Setting.goToLinkSoft(this, `/forget/${this.state.applicationName}`);
} }
if (res.data2.method === "POST") { if (res.data2.method === "POST") {
this.setState({ this.setState({
@ -961,9 +966,23 @@ class LoginPage extends React.Component {
const captchaProviderItems = this.getCaptchaProviderItems(application); const captchaProviderItems = this.getCaptchaProviderItems(application);
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always"); const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic"); const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
const provider = alwaysProviderItems.length > 0 const internetOnlyProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Internet-Only");
? alwaysProviderItems[0].provider
: dynamicProviderItems[0].provider; // Select provider based on the active captcha rule, not fixed priority
const captchaRule = this.getCaptchaRule(this.getApplicationObj());
let provider = null;
if (captchaRule === CaptchaRule.Always && alwaysProviderItems.length > 0) {
provider = alwaysProviderItems[0].provider;
} else if (captchaRule === CaptchaRule.Dynamic && dynamicProviderItems.length > 0) {
provider = dynamicProviderItems[0].provider;
} else if (captchaRule === CaptchaRule.InternetOnly && internetOnlyProviderItems.length > 0) {
provider = internetOnlyProviderItems[0].provider;
}
if (!provider) {
return null;
}
return <CaptchaModal return <CaptchaModal
owner={provider.owner} owner={provider.owner}

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Form, Input, Radio, Result, Row, Select, message} from "antd"; import {Button, Form, Input, Popover, Radio, Result, Row, Select, message} from "antd";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton"; import * as ProviderButton from "./ProviderButton";
@ -607,6 +607,7 @@ class SignupPage extends React.Component {
} }
} else if (signupItem.name === "Password") { } else if (signupItem.name === "Password") {
return ( return (
<Popover placement={window.innerWidth >= 960 ? "right" : "top"} content={this.state.passwordPopover} open={this.state.passwordPopoverOpen}>
<Form.Item <Form.Item
name="password" name="password"
className="signup-password" className="signup-password"
@ -627,8 +628,24 @@ class SignupPage extends React.Component {
]} ]}
hasFeedback hasFeedback
> >
<Input.Password className="signup-password-input" placeholder={signupItem.placeholder} /> <Input.Password className="signup-password-input" placeholder={signupItem.placeholder} onChange={(e) => {
this.setState({
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, e.target.value),
});
}}
onFocus={() => {
this.setState({
passwordPopoverOpen: application.organizationObj.passwordOptions?.length > 0,
passwordPopover: PasswordChecker.renderPasswordPopover(application.organizationObj.passwordOptions, this.form.current?.getFieldValue("password") ?? ""),
});
}}
onBlur={() => {
this.setState({
passwordPopoverOpen: false,
});
}} />
</Form.Item> </Form.Item>
</Popover>
); );
} else if (signupItem.name === "Confirm password") { } else if (signupItem.name === "Confirm password") {
return ( return (

View File

@ -31,9 +31,9 @@ export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application
const [mfaType, setMfaType] = useState(mfaProps.mfaType); const [mfaType, setMfaType] = useState(mfaProps.mfaType);
const [recoveryCode, setRecoveryCode] = useState(""); const [recoveryCode, setRecoveryCode] = useState("");
const verify = ({passcode}) => { const verify = ({passcode, enableMfaRemember}) => {
setLoading(true); setLoading(true);
const values = {...formValues, passcode}; const values = {...formValues, passcode, enableMfaRemember};
values["mfaType"] = mfaProps.mfaType; values["mfaType"] = mfaProps.mfaType;
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login; const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
loginFunction(values, authParams).then((res) => { loginFunction(values, authParams).then((res) => {

View File

@ -1,5 +1,5 @@
import {UserOutlined} from "@ant-design/icons"; import {UserOutlined} from "@ant-design/icons";
import {Button, Form, Input, Space} from "antd"; import {Button, Checkbox, Form, Input, Space} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import {CountryCodeSelect} from "../../common/select/CountryCodeSelect"; import {CountryCodeSelect} from "../../common/select/CountryCodeSelect";
@ -12,6 +12,13 @@ export const MfaVerifySmsForm = ({mfaProps, application, onFinish, method, user}
const [dest, setDest] = React.useState(""); const [dest, setDest] = React.useState("");
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleFinish = (values) => {
onFinish({
passcode: values.passcode,
enableMfaRemember: values.enableMfaRemember,
});
};
useEffect(() => { useEffect(() => {
if (method === mfaAuth) { if (method === mfaAuth) {
setDest(mfaProps.secret); setDest(mfaProps.secret);
@ -51,9 +58,10 @@ export const MfaVerifySmsForm = ({mfaProps, application, onFinish, method, user}
<Form <Form
form={form} form={form}
style={{width: "300px"}} style={{width: "300px"}}
onFinish={onFinish} onFinish={handleFinish}
initialValues={{ initialValues={{
countryCode: mfaProps.countryCode, countryCode: mfaProps.countryCode,
enableMfaRemember: false,
}} }}
> >
{isShowText() ? {isShowText() ?
@ -109,6 +117,14 @@ export const MfaVerifySmsForm = ({mfaProps, application, onFinish, method, user}
application={application} application={application}
/> />
</Form.Item> </Form.Item>
<Form.Item
name="enableMfaRemember"
valuePropName="checked"
>
<Checkbox>
{i18next.t("mfa:Remember this account for {hour} hours").replace("{hour}", mfaProps?.mfaRememberInHours)}
</Checkbox>
</Form.Item>
<Form.Item> <Form.Item>
<Button <Button
style={{marginTop: 24}} style={{marginTop: 24}}

View File

@ -1,5 +1,5 @@
import {CopyOutlined, UserOutlined} from "@ant-design/icons"; import {CopyOutlined} from "@ant-design/icons";
import {Button, Col, Form, Input, QRCode, Space} from "antd"; import {Button, Checkbox, Col, Form, Input, QRCode, Space} from "antd";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
@ -8,6 +8,13 @@ import * as Setting from "../../Setting";
export const MfaVerifyTotpForm = ({mfaProps, onFinish}) => { export const MfaVerifyTotpForm = ({mfaProps, onFinish}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleFinish = (values) => {
onFinish({
passcode: values.passcode,
enableMfaRemember: values.enableMfaRemember,
});
};
const renderSecret = () => { const renderSecret = () => {
if (!mfaProps.secret) { if (!mfaProps.secret) {
return null; return null;
@ -40,20 +47,31 @@ export const MfaVerifyTotpForm = ({mfaProps, onFinish}) => {
<Form <Form
form={form} form={form}
style={{width: "300px"}} style={{width: "300px"}}
onFinish={onFinish} onFinish={handleFinish}
initialValues={{
enableMfaRemember: false,
}}
> >
{renderSecret()} {renderSecret()}
<Form.Item <Form.Item
name="passcode" name="passcode"
rules={[{required: true, message: "Please input your passcode"}]} rules={[{required: true, message: "Please input your passcode"}]}
> >
<Input <Input.OTP
style={{marginTop: 24}} style={{marginTop: 24}}
prefix={<UserOutlined />} onChange={() => {
placeholder={i18next.t("mfa:Passcode")} form.submit();
autoComplete="off" }}
/> />
</Form.Item> </Form.Item>
<Form.Item
name="enableMfaRemember"
valuePropName="checked"
>
<Checkbox>
{i18next.t("mfa:Remember this account for {hour} hours").replace("{hour}", mfaProps?.mfaRememberInHours)}
</Checkbox>
</Form.Item>
<Form.Item> <Form.Item>
<Button <Button
style={{marginTop: 24}} style={{marginTop: 24}}

View File

@ -13,6 +13,8 @@
// limitations under the License. // limitations under the License.
import i18next from "i18next"; import i18next from "i18next";
import React from "react";
import {CheckCircleTwoTone, CloseCircleTwoTone} from "@ant-design/icons";
function isValidOption_AtLeast6(password) { function isValidOption_AtLeast6(password) {
if (password.length < 6) { if (password.length < 6) {
@ -52,6 +54,33 @@ function isValidOption_NoRepeat(password) {
return ""; return "";
} }
const checkers = {
AtLeast6: isValidOption_AtLeast6,
AtLeast8: isValidOption_AtLeast8,
Aa123: isValidOption_Aa123,
SpecialChar: isValidOption_SpecialChar,
NoRepeat: isValidOption_NoRepeat,
};
function getOptionDescription(option, password) {
switch (option) {
case "AtLeast6": return i18next.t("user:The password must have at least 6 characters");
case "AtLeast8": return i18next.t("user:The password must have at least 8 characters");
case "Aa123": return i18next.t("user:The password must contain at least one uppercase letter, one lowercase letter and one digit");
case "SpecialChar": return i18next.t("user:The password must contain at least one special character");
case "NoRepeat": return i18next.t("user:The password must not contain any repeated characters");
}
}
export function renderPasswordPopover(options, password) {
return <div style={{width: 240}} >
{options.map((option, idx) => {
return <div key={idx}>{checkers[option](password) === "" ? <CheckCircleTwoTone twoToneColor={"#52c41a"} /> :
<CloseCircleTwoTone twoToneColor={"#ff4d4f"} />} {getOptionDescription(option, password)}</div>;
})}
</div>;
}
export function checkPasswordComplexity(password, options) { export function checkPasswordComplexity(password, options) {
if (password.length === 0) { if (password.length === 0) {
return i18next.t("login:Please input your password!"); return i18next.t("login:Please input your password!");
@ -61,14 +90,6 @@ export function checkPasswordComplexity(password, options) {
return ""; return "";
} }
const checkers = {
AtLeast6: isValidOption_AtLeast6,
AtLeast8: isValidOption_AtLeast8,
Aa123: isValidOption_Aa123,
SpecialChar: isValidOption_SpecialChar,
NoRepeat: isValidOption_NoRepeat,
};
for (const option of options) { for (const option of options) {
const checkerFunc = checkers[option]; const checkerFunc = checkers[option];
if (checkerFunc) { if (checkerFunc) {

View File

@ -181,4 +181,5 @@ export const CaptchaRule = {
Always: "Always", Always: "Always",
Never: "Never", Never: "Never",
Dynamic: "Dynamic", Dynamic: "Dynamic",
InternetOnly: "Internet-Only",
}; };

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Input, Modal, Row} from "antd"; import {Button, Col, Input, Modal, Popover, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
import * as UserBackend from "../../backend/UserBackend"; import * as UserBackend from "../../backend/UserBackend";
@ -35,6 +35,8 @@ export const PasswordModal = (props) => {
const [rePasswordValid, setRePasswordValid] = React.useState(false); const [rePasswordValid, setRePasswordValid] = React.useState(false);
const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState(""); const [newPasswordErrorMessage, setNewPasswordErrorMessage] = React.useState("");
const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState(""); const [rePasswordErrorMessage, setRePasswordErrorMessage] = React.useState("");
const [passwordPopoverOpen, setPasswordPopoverOpen] = React.useState(false);
const [passwordPopover, setPasswordPopover] = React.useState();
React.useEffect(() => { React.useEffect(() => {
if (organization) { if (organization) {
@ -130,12 +132,26 @@ export const PasswordModal = (props) => {
</Row> </Row>
) : null} ) : null}
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>
<Popover placement={window.innerWidth >= 960 ? "right" : "top"} content={passwordPopover} open={passwordPopoverOpen}>
<Input.Password <Input.Password
addonBefore={i18next.t("user:New Password")} addonBefore={i18next.t("user:New Password")}
placeholder={i18next.t("user:input password")} placeholder={i18next.t("user:input password")}
onChange={(e) => {handleNewPassword(e.target.value);}} onChange={(e) => {
handleNewPassword(e.target.value);
setPasswordPopoverOpen(passwordOptions?.length > 0);
setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, e.target.value));
}}
onFocus={() => {
setPasswordPopoverOpen(passwordOptions?.length > 0);
setPasswordPopover(PasswordChecker.renderPasswordPopover(passwordOptions, newPassword));
}}
onBlur={() => {
setPasswordPopoverOpen(false);
}}
status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined} status={(!newPasswordValid && newPasswordErrorMessage) ? "error" : undefined}
/> />
</Popover>
</Row> </Row>
{!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>} {!newPasswordValid && newPasswordErrorMessage && <div style={{color: "red", marginTop: "-20px"}}>{newPasswordErrorMessage}</div>}
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Přizpůsobit hlavičku vstupní stránky vaší aplikace", "Header HTML - Tooltip": "Přizpůsobit hlavičku vstupní stránky vaší aplikace",
"Incremental": "Inkrementální", "Incremental": "Inkrementální",
"Input": "Vstup", "Input": "Vstup",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Kód pozvánky", "Invitation code": "Kód pozvánky",
"Left": "Vlevo", "Left": "Vlevo",
"Logged in successfully": "Úspěšně přihlášen", "Logged in successfully": "Úspěšně přihlášen",
"Logged out successfully": "Úspěšně odhlášen", "Logged out successfully": "Úspěšně odhlášen",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Nová aplikace", "New Application": "Nová aplikace",
"No verification": "Bez ověření", "No verification": "Bez ověření",
@ -579,13 +582,13 @@
"Multi-factor recover": "Obnovení dvoufaktorového ověřování", "Multi-factor recover": "Obnovení dvoufaktorového ověřování",
"Multi-factor recover description": "Popis obnovení dvoufaktorového ověřování", "Multi-factor recover description": "Popis obnovení dvoufaktorového ověřování",
"Or copy the secret to your Authenticator App": "Nebo zkopírujte tajný kód do své aplikace Authenticator", "Or copy the secret to your Authenticator App": "Nebo zkopírujte tajný kód do své aplikace Authenticator",
"Passcode": "Přístupový kód",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Nejprve prosím spojte svůj email, systém automaticky použije tento email pro dvoufaktorové ověřování", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Nejprve prosím spojte svůj email, systém automaticky použije tento email pro dvoufaktorové ověřování",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Nejprve prosím spojte svůj telefon, systém automaticky použije tento telefon pro dvoufaktorové ověřování", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Nejprve prosím spojte svůj telefon, systém automaticky použije tento telefon pro dvoufaktorové ověřování",
"Please confirm the information below": "Potvrďte prosím níže uvedené informace", "Please confirm the information below": "Potvrďte prosím níže uvedené informace",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Uložte si tento obnovovací kód. Pokud vaše zařízení nemůže poskytnout ověřovací kód, můžete resetovat dvoufaktorové ověřování pomocí tohoto kódu", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Uložte si tento obnovovací kód. Pokud vaše zařízení nemůže poskytnout ověřovací kód, můžete resetovat dvoufaktorové ověřování pomocí tohoto kódu",
"Protect your account with Multi-factor authentication": "Chraňte svůj účet pomocí dvoufaktorového ověřování", "Protect your account with Multi-factor authentication": "Chraňte svůj účet pomocí dvoufaktorového ověřování",
"Recovery code": "Obnovovací kód", "Recovery code": "Obnovovací kód",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Naskenujte QR kód pomocí aplikace Authenticator", "Scan the QR code with your Authenticator App": "Naskenujte QR kód pomocí aplikace Authenticator",
"Set preferred": "Nastavit jako preferované", "Set preferred": "Nastavit jako preferované",
"Setup": "Nastavení", "Setup": "Nastavení",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Links", "Left": "Links",
"Logged in successfully": "Erfolgreich eingeloggt", "Logged in successfully": "Erfolgreich eingeloggt",
"Logged out successfully": "Erfolgreich ausgeloggt", "Logged out successfully": "Erfolgreich ausgeloggt",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Neue Anwendung", "New Application": "Neue Anwendung",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "Configures the duration that a account is remembered as trusted after a successful MFA login",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Izquierda", "Left": "Izquierda",
"Logged in successfully": "Acceso satisfactorio", "Logged in successfully": "Acceso satisfactorio",
"Logged out successfully": "Cerró sesión exitosamente", "Logged out successfully": "Cerró sesión exitosamente",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Nueva aplicación", "New Application": "Nueva aplicación",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "کد head صفحه ورود برنامه خود را سفارشی کنید", "Header HTML - Tooltip": "کد head صفحه ورود برنامه خود را سفارشی کنید",
"Incremental": "افزایشی", "Incremental": "افزایشی",
"Input": "ورودی", "Input": "ورودی",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "کد دعوت", "Invitation code": "کد دعوت",
"Left": "چپ", "Left": "چپ",
"Logged in successfully": "با موفقیت وارد شدید", "Logged in successfully": "با موفقیت وارد شدید",
"Logged out successfully": "با موفقیت خارج شدید", "Logged out successfully": "با موفقیت خارج شدید",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "انتخاب‌های متعدد", "Multiple Choices": "انتخاب‌های متعدد",
"New Application": "برنامه جدید", "New Application": "برنامه جدید",
"No verification": "بدون تأیید", "No verification": "بدون تأیید",
@ -579,13 +582,13 @@
"Multi-factor recover": "بازیابی چندعاملی", "Multi-factor recover": "بازیابی چندعاملی",
"Multi-factor recover description": "توضیح بازیابی چندعاملی", "Multi-factor recover description": "توضیح بازیابی چندعاملی",
"Or copy the secret to your Authenticator App": "یا راز را به برنامه تأیید هویت خود کپی کنید", "Or copy the secret to your Authenticator App": "یا راز را به برنامه تأیید هویت خود کپی کنید",
"Passcode": "کد عبور",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "لطفاً ابتدا ایمیل خود را متصل کنید، سیستم به‌طور خودکار از ایمیل برای احراز هویت چندعاملی استفاده می‌کند", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "لطفاً ابتدا ایمیل خود را متصل کنید، سیستم به‌طور خودکار از ایمیل برای احراز هویت چندعاملی استفاده می‌کند",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "لطفاً ابتدا تلفن خود را متصل کنید، سیستم به‌طور خودکار از تلفن برای احراز هویت چندعاملی استفاده می‌کند", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "لطفاً ابتدا تلفن خود را متصل کنید، سیستم به‌طور خودکار از تلفن برای احراز هویت چندعاملی استفاده می‌کند",
"Please confirm the information below": "لطفاً اطلاعات زیر را تأیید کنید", "Please confirm the information below": "لطفاً اطلاعات زیر را تأیید کنید",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "لطفاً این کد بازیابی را ذخیره کنید. هنگامی که دستگاه شما نتواند کد تأیید ارائه دهد، می‌توانید احراز هویت mfa را با این کد بازیابی تنظیم مجدد کنید", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "لطفاً این کد بازیابی را ذخیره کنید. هنگامی که دستگاه شما نتواند کد تأیید ارائه دهد، می‌توانید احراز هویت mfa را با این کد بازیابی تنظیم مجدد کنید",
"Protect your account with Multi-factor authentication": "حساب خود را با احراز هویت چندعاملی محافظت کنید", "Protect your account with Multi-factor authentication": "حساب خود را با احراز هویت چندعاملی محافظت کنید",
"Recovery code": "کد بازیابی", "Recovery code": "کد بازیابی",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "کد QR را با برنامه تأیید هویت خود اسکن کنید", "Scan the QR code with your Authenticator App": "کد QR را با برنامه تأیید هویت خود اسکن کنید",
"Set preferred": "تنظیم به‌عنوان مورد علاقه", "Set preferred": "تنظیم به‌عنوان مورد علاقه",
"Setup": "راه‌اندازی", "Setup": "راه‌اندازی",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incrémentale", "Incremental": "Incrémentale",
"Input": "Saisie", "Input": "Saisie",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Code d'invitation", "Invitation code": "Code d'invitation",
"Left": "Gauche", "Left": "Gauche",
"Logged in successfully": "Connexion réussie", "Logged in successfully": "Connexion réussie",
"Logged out successfully": "Déconnexion réussie", "Logged out successfully": "Déconnexion réussie",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Nouvelle application", "New Application": "Nouvelle application",
"No verification": "Aucune vérification", "No verification": "Aucune vérification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Restauration de l'authentification multifacteur", "Multi-factor recover": "Restauration de l'authentification multifacteur",
"Multi-factor recover description": "Description de la restauration de l'authentification multifacteur", "Multi-factor recover description": "Description de la restauration de l'authentification multifacteur",
"Or copy the secret to your Authenticator App": "Ou copiez la clé secrète dans votre application d'authentification", "Or copy the secret to your Authenticator App": "Ou copiez la clé secrète dans votre application d'authentification",
"Passcode": "Code d'accès",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Veuillez lier votre e-mail en premier, le système l'utilisera automatiquement pour l'authentification multifacteur", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Veuillez lier votre e-mail en premier, le système l'utilisera automatiquement pour l'authentification multifacteur",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Veuillez lier votre numéro de téléphone en premier, le système l'utilisera automatiquement pour l'authentification multifacteur", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Veuillez lier votre numéro de téléphone en premier, le système l'utilisera automatiquement pour l'authentification multifacteur",
"Please confirm the information below": "Veuillez confirmer les informations ci-dessous", "Please confirm the information below": "Veuillez confirmer les informations ci-dessous",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Veuillez enregistrer ce code de récupération. Si votre appareil ne peut pas vous fournir un code d'authentification, vous pourrez réinitialiser l'authentification multifacteur avec ce code de récupération", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Veuillez enregistrer ce code de récupération. Si votre appareil ne peut pas vous fournir un code d'authentification, vous pourrez réinitialiser l'authentification multifacteur avec ce code de récupération",
"Protect your account with Multi-factor authentication": "Protégez votre compte avec l'authentification multifacteur", "Protect your account with Multi-factor authentication": "Protégez votre compte avec l'authentification multifacteur",
"Recovery code": "Code de récupération", "Recovery code": "Code de récupération",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scannez le QR code avec votre application d'authentification", "Scan the QR code with your Authenticator App": "Scannez le QR code avec votre application d'authentification",
"Set preferred": "Définir comme préféré", "Set preferred": "Définir comme préféré",
"Setup": "Configurer", "Setup": "Configurer",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Kiri", "Left": "Kiri",
"Logged in successfully": "Berhasil masuk", "Logged in successfully": "Berhasil masuk",
"Logged out successfully": "Berhasil keluar dari sistem", "Logged out successfully": "Berhasil keluar dari sistem",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "The duration for which the account is remembered as trusted after a successful MFA login",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Aplikasi Baru", "New Application": "Aplikasi Baru",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "左", "Left": "左",
"Logged in successfully": "正常にログインしました", "Logged in successfully": "正常にログインしました",
"Logged out successfully": "正常にログアウトしました", "Logged out successfully": "正常にログアウトしました",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "新しいアプリケーション", "New Application": "新しいアプリケーション",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "왼쪽", "Left": "왼쪽",
"Logged in successfully": "성공적으로 로그인했습니다", "Logged in successfully": "성공적으로 로그인했습니다",
"Logged out successfully": "로그아웃이 성공적으로 되었습니다", "Logged out successfully": "로그아웃이 성공적으로 되었습니다",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "새로운 응용 프로그램", "New Application": "새로운 응용 프로그램",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Código de convite", "Invitation code": "Código de convite",
"Left": "Esquerda", "Left": "Esquerda",
"Logged in successfully": "Login realizado com sucesso", "Logged in successfully": "Login realizado com sucesso",
"Logged out successfully": "Logout realizado com sucesso", "Logged out successfully": "Logout realizado com sucesso",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Nova Aplicação", "New Application": "Nova Aplicação",
"No verification": "Sem verificação", "No verification": "Sem verificação",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Código de acesso",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Последовательный", "Incremental": "Последовательный",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Код приглашения", "Invitation code": "Код приглашения",
"Left": "Левый", "Left": "Левый",
"Logged in successfully": "Успешный вход в систему", "Logged in successfully": "Успешный вход в систему",
"Logged out successfully": "Успешный выход из системы", "Logged out successfully": "Успешный выход из системы",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Новое приложение", "New Application": "Новое приложение",
"No verification": "Нет верификации", "No verification": "Нет верификации",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Vlastný HTML kód pre hlavičku vašej vstupnej stránky aplikácie", "Header HTML - Tooltip": "Vlastný HTML kód pre hlavičku vašej vstupnej stránky aplikácie",
"Incremental": "Postupný", "Incremental": "Postupný",
"Input": "Vstup", "Input": "Vstup",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Kód pozvania", "Invitation code": "Kód pozvania",
"Left": "Vľavo", "Left": "Vľavo",
"Logged in successfully": "Úspešne prihlásený", "Logged in successfully": "Úspešne prihlásený",
"Logged out successfully": "Úspešne odhlásený", "Logged out successfully": "Úspešne odhlásený",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Nová aplikácia", "New Application": "Nová aplikácia",
"No verification": "Bez overenia", "No verification": "Bez overenia",
@ -579,13 +582,13 @@
"Multi-factor recover": "Obnova viacfaktorovej autentifikácie", "Multi-factor recover": "Obnova viacfaktorovej autentifikácie",
"Multi-factor recover description": "Popis obnovy viacfaktorovej autentifikácie", "Multi-factor recover description": "Popis obnovy viacfaktorovej autentifikácie",
"Or copy the secret to your Authenticator App": "Alebo skopírujte tajomstvo do svojej aplikácie na autentifikáciu", "Or copy the secret to your Authenticator App": "Alebo skopírujte tajomstvo do svojej aplikácie na autentifikáciu",
"Passcode": "Kód",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Najskôr pripojte svoj email, systém automaticky použije mail na viacfaktorovú autentifikáciu", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Najskôr pripojte svoj email, systém automaticky použije mail na viacfaktorovú autentifikáciu",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Najskôr pripojte svoj telefón, systém automaticky použije telefón na viacfaktorovú autentifikáciu", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Najskôr pripojte svoj telefón, systém automaticky použije telefón na viacfaktorovú autentifikáciu",
"Please confirm the information below": "Potvrďte informácie nižšie", "Please confirm the information below": "Potvrďte informácie nižšie",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Uložte si tento obnovovací kód. Keď vaše zariadenie nebude schopné poskytnúť overovací kód, môžete obnoviť MFA autentifikáciu pomocou tohto obnovovacieho kódu", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Uložte si tento obnovovací kód. Keď vaše zariadenie nebude schopné poskytnúť overovací kód, môžete obnoviť MFA autentifikáciu pomocou tohto obnovovacieho kódu",
"Protect your account with Multi-factor authentication": "Chráňte svoj účet pomocou viacfaktorovej autentifikácie", "Protect your account with Multi-factor authentication": "Chráňte svoj účet pomocou viacfaktorovej autentifikácie",
"Recovery code": "Obnovovací kód", "Recovery code": "Obnovovací kód",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Naskenujte QR kód pomocou svojej aplikácie na autentifikáciu", "Scan the QR code with your Authenticator App": "Naskenujte QR kód pomocou svojej aplikácie na autentifikáciu",
"Set preferred": "Nastaviť ako preferované", "Set preferred": "Nastaviť ako preferované",
"Setup": "Nastaviť", "Setup": "Nastaviť",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully", "Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully", "Logged out successfully": "Logged out successfully",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Incremental", "Incremental": "Incremental",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Davet Kodu", "Invitation code": "Davet Kodu",
"Left": "Sol", "Left": "Sol",
"Logged in successfully": "Başarıyla giriş yapıldı", "Logged in successfully": "Başarıyla giriş yapıldı",
"Logged out successfully": "Başarıyla çıkış yapıldı", "Logged out successfully": "Başarıyla çıkış yapıldı",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "New Application", "New Application": "New Application",
"No verification": "No verification", "No verification": "No verification",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Veya kod 'u Authenticator uygulamasından kopyalayın", "Or copy the secret to your Authenticator App": "Veya kod 'u Authenticator uygulamasından kopyalayın",
"Passcode": "Parola",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Lütfen aşağıdaki bilgileri doğrulayın", "Please confirm the information below": "Lütfen aşağıdaki bilgileri doğrulayın",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Lütfen bu kurtarma kodlarını kaydedin. Cihazınızdan yetkilendirme kodları oluşturamazsanız bu kodları kullanarak sorunu çözebilirsiniz", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Lütfen bu kurtarma kodlarını kaydedin. Cihazınızdan yetkilendirme kodları oluşturamazsanız bu kodları kullanarak sorunu çözebilirsiniz",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Kurtarma kodu", "Recovery code": "Kurtarma kodu",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Bu QR kodunu kimlik doğrulama uygulamanızla tarayın", "Scan the QR code with your Authenticator App": "Bu QR kodunu kimlik doğrulama uygulamanızla tarayın",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Налаштуйте тег head на сторінці входу до програми", "Header HTML - Tooltip": "Налаштуйте тег head на сторінці входу до програми",
"Incremental": "Інкрементний", "Incremental": "Інкрементний",
"Input": "Введення", "Input": "Введення",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Код запрошення", "Invitation code": "Код запрошення",
"Left": "Ліворуч", "Left": "Ліворуч",
"Logged in successfully": "Успішно ввійшли", "Logged in successfully": "Успішно ввійшли",
"Logged out successfully": "Успішно вийшов", "Logged out successfully": "Успішно вийшов",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Нова заявка", "New Application": "Нова заявка",
"No verification": "Без підтвердження", "No verification": "Без підтвердження",
@ -579,13 +582,13 @@
"Multi-factor recover": "Багатофакторне відновлення", "Multi-factor recover": "Багатофакторне відновлення",
"Multi-factor recover description": "Опис багатофакторного відновлення", "Multi-factor recover description": "Опис багатофакторного відновлення",
"Or copy the secret to your Authenticator App": "Або скопіюйте секрет у програму Authenticator", "Or copy the secret to your Authenticator App": "Або скопіюйте секрет у програму Authenticator",
"Passcode": "Пароль",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Спочатку прив’яжіть свою електронну адресу, система автоматично використовуватиме її для багатофакторної автентифікації", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Спочатку прив’яжіть свою електронну адресу, система автоматично використовуватиме її для багатофакторної автентифікації",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Спочатку прив’яжіть свій телефон, система автоматично використовує телефон для багатофакторної автентифікації", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Спочатку прив’яжіть свій телефон, система автоматично використовує телефон для багатофакторної автентифікації",
"Please confirm the information below": "Підтвердьте інформацію нижче", "Please confirm the information below": "Підтвердьте інформацію нижче",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Будь ласка, збережіть цей код відновлення. ", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Будь ласка, збережіть цей код відновлення. ",
"Protect your account with Multi-factor authentication": "Захистіть свій обліковий запис за допомогою багатофакторної автентифікації", "Protect your account with Multi-factor authentication": "Захистіть свій обліковий запис за допомогою багатофакторної автентифікації",
"Recovery code": "Код відновлення", "Recovery code": "Код відновлення",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Відскануйте QR-код за допомогою програми Authenticator", "Scan the QR code with your Authenticator App": "Відскануйте QR-код за допомогою програми Authenticator",
"Set preferred": "Встановити перевагу", "Set preferred": "Встановити перевагу",
"Setup": "Налаштування", "Setup": "Налаштування",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "Custom the head tag of your application entry page", "Header HTML - Tooltip": "Custom the head tag of your application entry page",
"Incremental": "Tăng", "Incremental": "Tăng",
"Input": "Input", "Input": "Input",
"Internet-Only": "Internet-Only",
"Invalid characters in application name": "Invalid characters in application name", "Invalid characters in application name": "Invalid characters in application name",
"Invitation code": "Invitation code", "Invitation code": "Invitation code",
"Left": "Trái", "Left": "Trái",
"Logged in successfully": "Đăng nhập thành công", "Logged in successfully": "Đăng nhập thành công",
"Logged out successfully": "Đã đăng xuất thành công", "Logged out successfully": "Đã đăng xuất thành công",
"MFA remember time": "MFA remember time",
"MFA remember time - Tooltip": "MFA remember time - Tooltip",
"Multiple Choices": "Multiple Choices", "Multiple Choices": "Multiple Choices",
"New Application": "Ứng dụng mới", "New Application": "Ứng dụng mới",
"No verification": "Không xác minh", "No verification": "Không xác minh",
@ -579,13 +582,13 @@
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App", "Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please confirm the information below": "Please confirm the information below", "Please confirm the information below": "Please confirm the information below",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Remember this account for {hour} hours": "Remember this account for {hour} hours",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App", "Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",

View File

@ -76,11 +76,14 @@
"Header HTML - Tooltip": "自定义应用页面的head标签", "Header HTML - Tooltip": "自定义应用页面的head标签",
"Incremental": "递增", "Incremental": "递增",
"Input": "输入", "Input": "输入",
"Internet-Only": "外网启用",
"Invalid characters in application name": "应用名称内有非法字符", "Invalid characters in application name": "应用名称内有非法字符",
"Invitation code": "邀请码", "Invitation code": "邀请码",
"Left": "居左", "Left": "居左",
"Logged in successfully": "登录成功", "Logged in successfully": "登录成功",
"Logged out successfully": "登出成功", "Logged out successfully": "登出成功",
"MFA remember time": "MFA记住时间",
"MFA remember time - Tooltip": "配置MFA登录成功后帐户被记住为受信任的持续时间",
"Multiple Choices": "多选", "Multiple Choices": "多选",
"New Application": "添加应用", "New Application": "添加应用",
"No verification": "不校验", "No verification": "不校验",
@ -579,13 +582,13 @@
"Multi-factor recover": "重置多因素认证", "Multi-factor recover": "重置多因素认证",
"Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份", "Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份",
"Or copy the secret to your Authenticator App": "或者将这个密钥复制到你的身份验证应用中", "Or copy the secret to your Authenticator App": "或者将这个密钥复制到你的身份验证应用中",
"Passcode": "认证码",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式",
"Please confirm the information below": "请确认以下信息", "Please confirm the information below": "请确认以下信息",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证",
"Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户", "Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户",
"Recovery code": "恢复码", "Recovery code": "恢复码",
"Remember this account for {hour} hours": "记住这个账户 {hour} 小时",
"Scan the QR code with your Authenticator App": "用你的身份验证应用扫描二维码", "Scan the QR code with your Authenticator App": "用你的身份验证应用扫描二维码",
"Set preferred": "设为首选", "Set preferred": "设为首选",
"Setup": "设置", "Setup": "设置",

View File

@ -110,6 +110,7 @@ class AccountTable extends React.Component {
{name: "Managed accounts", label: i18next.t("user:Managed accounts")}, {name: "Managed accounts", label: i18next.t("user:Managed accounts")},
{name: "Face ID", label: i18next.t("user:Face ID")}, {name: "Face ID", label: i18next.t("user:Face ID")},
{name: "MFA accounts", label: i18next.t("user:MFA accounts")}, {name: "MFA accounts", label: i18next.t("user:MFA accounts")},
{name: "MFA items", label: i18next.t("general:MFA items")},
]; ];
}; };

View File

@ -255,6 +255,7 @@ class ProviderTable extends React.Component {
<Option key="None" value="None">{i18next.t("general:None")}</Option> <Option key="None" value="None">{i18next.t("general:None")}</Option>
<Option key="Dynamic" value="Dynamic">{i18next.t("application:Dynamic")}</Option> <Option key="Dynamic" value="Dynamic">{i18next.t("application:Dynamic")}</Option>
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option> <Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
<Option key="Internet-Only" value="Internet-Only">{i18next.t("application:Internet-Only")}</Option>
</Select> </Select>
); );
} else if (record.provider?.category === "SMS" || record.provider?.category === "Email") { } else if (record.provider?.category === "SMS" || record.provider?.category === "Email") {