mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 00:50:28 +08:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
9bbe5afb7c | |||
b42391c6ce | |||
fb035a5353 | |||
b1f68a60a4 | |||
201d704a31 | |||
bf91ad6c97 | |||
3ccc0339c7 | |||
1f2b0a3587 | |||
0b3feb0d5f |
@ -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 {
|
||||||
|
@ -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
|
||||||
@ -555,8 +555,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 {
|
||||||
@ -1222,27 +1225,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 {
|
||||||
captchaEnabled := false
|
c.ResponseError("application not found")
|
||||||
if user != nil {
|
return
|
||||||
var failedSigninLimit int
|
|
||||||
failedSigninLimit, _, err = object.GetFailedSigninConfigByUser(user)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.SigninWrongTimes >= failedSigninLimit {
|
|
||||||
captchaEnabled = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
captchaEnabled, err := object.CheckToEnableCaptcha(application, organization, userId, clientIp)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
c.ResponseOk(captchaEnabled)
|
c.ResponseOk(captchaEnabled)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback
|
// Callback
|
||||||
|
@ -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())
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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, ""))
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -252,12 +252,12 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
|
|||||||
credManager := cred.GetCredManager(passwordType)
|
credManager := cred.GetCredManager(passwordType)
|
||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
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 resetUserSigninErrorTimes(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,31 +593,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 == "Dynamic" {
|
if providerItem.Rule == "Internet-Only" {
|
||||||
user, err := GetUserByFields(organization, username)
|
if util.IsInternetIp(clientIp) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if providerItem.Rule == "Dynamic" {
|
||||||
|
user, err := GetUserByFields(organization, username)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
failedSigninLimit, _, err := GetFailedSigninConfigByUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
failedSigninLimit := application.FailedSigninLimit
|
return user.SigninWrongTimes >= failedSigninLimit, nil
|
||||||
if failedSigninLimit == 0 {
|
|
||||||
failedSigninLimit = DefaultFailedSigninLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil
|
|
||||||
}
|
}
|
||||||
return providerItem.Rule == "Always", nil
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return providerItem.Rule == "Always", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -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")
|
||||||
|
@ -222,7 +222,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 +536,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
|
||||||
|
@ -212,6 +212,7 @@ 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"`
|
||||||
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
||||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
}
|
}
|
||||||
@ -795,7 +796,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
|
47
util/network.go
Normal file
47
util/network.go
Normal 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()
|
||||||
|
}
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 : (
|
||||||
|
@ -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: {
|
||||||
|
@ -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;
|
||||||
|
@ -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}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {CopyOutlined, UserOutlined} from "@ant-design/icons";
|
import {CopyOutlined} from "@ant-design/icons";
|
||||||
import {Button, Col, Form, Input, QRCode, Space} from "antd";
|
import {Button, 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";
|
||||||
@ -47,11 +47,11 @@ export const MfaVerifyTotpForm = ({mfaProps, onFinish}) => {
|
|||||||
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>
|
<Form.Item>
|
||||||
|
@ -181,4 +181,5 @@ export const CaptchaRule = {
|
|||||||
Always: "Always",
|
Always: "Always",
|
||||||
Never: "Never",
|
Never: "Never",
|
||||||
Dynamic: "Dynamic",
|
Dynamic: "Dynamic",
|
||||||
|
InternetOnly: "Internet-Only",
|
||||||
};
|
};
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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": "چپ",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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": "左",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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": "왼쪽",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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": "Левый",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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": "Ліворуч",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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",
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"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": "居左",
|
||||||
|
@ -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")},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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") {
|
||||||
|
Reference in New Issue
Block a user