mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-17 21:24:53 +08:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c190634cf3 | ||
![]() |
f7559aa040 | ||
![]() |
1e0b709c73 | ||
![]() |
c0800b7fb3 | ||
![]() |
6fcdad2100 | ||
![]() |
69d26d5c21 | ||
![]() |
94e6b5ecb8 | ||
![]() |
95e8bdcd36 | ||
![]() |
6f1f93725e | ||
![]() |
7ae067e369 | ||
![]() |
dde936e935 | ||
![]() |
fb561a98c8 | ||
![]() |
7cd8f030ee |
@@ -29,5 +29,6 @@ radiusServerPort = 1812
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
frontendBaseDir = "../casdoor"
|
||||
frontendBaseDir = "../cc_0"
|
@@ -116,6 +116,13 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
err = object.CheckEntryIp(clientIp, nil, application, organization, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
|
@@ -110,6 +110,9 @@ func (c *ApiController) GetApplication() {
|
||||
}
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
}
|
||||
|
||||
@@ -229,6 +232,11 @@ func (c *ApiController) UpdateApplication() {
|
||||
return
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -259,6 +267,11 @@ func (c *ApiController) AddApplication() {
|
||||
return
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@@ -55,6 +55,13 @@ func tokenToResponse(token *object.Token) *Response {
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
||||
userId := user.GetId()
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
err := object.CheckEntryIp(clientIp, user, application, application.OrganizationObj, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
allowed, err := object.CheckLoginPermission(userId, application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
@@ -256,6 +263,9 @@ func (c *ApiController) GetApplicationLogin() {
|
||||
}
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
|
||||
|
||||
application = object.GetMaskedApplication(application, "")
|
||||
if msg != "" {
|
||||
c.ResponseError(msg, application)
|
||||
|
@@ -119,6 +119,11 @@ func (c *ApiController) UpdateOrganization() {
|
||||
return
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -149,6 +154,11 @@ func (c *ApiController) AddOrganization() {
|
||||
return
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@@ -182,6 +182,10 @@ func (c *ApiController) BuyProduct() {
|
||||
paidUserName := c.Input().Get("userName")
|
||||
owner, _ := util.GetOwnerAndNameFromId(id)
|
||||
userId := util.GetId(owner, paidUserName)
|
||||
if paidUserName != "" && !c.IsAdmin() {
|
||||
c.ResponseError(c.T("general:Only admin user can specify user"))
|
||||
return
|
||||
}
|
||||
if paidUserName == "" {
|
||||
userId = c.GetSessionUsername()
|
||||
}
|
||||
|
@@ -364,7 +364,8 @@ func (c *ApiController) AddUser() {
|
||||
return
|
||||
}
|
||||
|
||||
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
|
||||
emptyUser := object.User{}
|
||||
msg := object.CheckUpdateUser(&emptyUser, &user, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
@@ -489,7 +490,12 @@ func (c *ApiController) SetPassword() {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
if userId != c.GetSession("verifiedUserId") {
|
||||
c.ResponseError(c.T("general:Wrong userId"))
|
||||
return
|
||||
}
|
||||
c.SetSession("verifiedCode", "")
|
||||
c.SetSession("verifiedUserId", "")
|
||||
}
|
||||
|
||||
targetUser, err := object.GetUser(userId)
|
||||
@@ -535,6 +541,23 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByUser(targetUser)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:the application for user %s is not found"), userId))
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
err = object.CheckEntryIp(clientIp, targetUser, application, organization, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
targetUser.Password = newPassword
|
||||
targetUser.UpdateUserPassword(organization)
|
||||
targetUser.NeedUpdatePassword = false
|
||||
|
@@ -533,5 +533,6 @@ func (c *ApiController) VerifyCode() {
|
||||
}
|
||||
|
||||
c.SetSession("verifiedCode", authForm.Code)
|
||||
c.SetSession("verifiedUserId", user.GetId())
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@@ -15,10 +15,10 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
|
||||
"The application: %s does not exist": "Приложение: %s не существует",
|
||||
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application",
|
||||
"The login method: login with LDAP is not enabled for the application": "Метод входа в систему: вход с помощью LDAP не включен для приложения",
|
||||
"The login method: login with SMS is not enabled for the application": "Метод входа: вход с помощью SMS не включен для приложения",
|
||||
"The login method: login with email is not enabled for the application": "Метод входа: вход с помощью электронной почты не включен для приложения",
|
||||
"The login method: login with face is not enabled for the application": "Метод входа: вход с помощью лица не включен для приложения",
|
||||
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
|
||||
"The organization: %s does not exist": "The organization: %s does not exist",
|
||||
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
|
||||
@@ -53,16 +53,16 @@
|
||||
"Phone already exists": "Телефон уже существует",
|
||||
"Phone cannot be empty": "Телефон не может быть пустым",
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code",
|
||||
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Please register using the email corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя электронную почту, соответствующую коду приглашения",
|
||||
"Please register using the phone corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь по телефону, соответствующему коду приглашения",
|
||||
"Please register using the username corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя имя пользователя, соответствующее коду приглашения",
|
||||
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
|
||||
"The user: %s doesn't exist in LDAP server": "Пользователь %s не существует на LDAP сервере",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
||||
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex",
|
||||
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"",
|
||||
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Значение \\\"%s\\\" для поля аккаунта \\\"%s\\\" не соответствует регулярному значению",
|
||||
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Значение \\\"%s\\\" поля регистрации \\\"%s\\\" не соответствует регулярному выражению приложения \\\"%s\\\"",
|
||||
"Username already exists": "Имя пользователя уже существует",
|
||||
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
||||
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
|
||||
@@ -78,11 +78,11 @@
|
||||
"general": {
|
||||
"Missing parameter": "Отсутствующий параметр",
|
||||
"Please login first": "Пожалуйста, сначала войдите в систему",
|
||||
"The organization: %s should have one application at least": "The organization: %s should have one application at least",
|
||||
"The organization: %s should have one application at least": "Организация: %s должна иметь хотя бы одно приложение",
|
||||
"The user: %s doesn't exist": "Пользователь %s не существует",
|
||||
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "эта операция не разрешена в демо-режиме",
|
||||
"this operation requires administrator to perform": "this operation requires administrator to perform"
|
||||
"this operation requires administrator to perform": "для выполнения этой операции требуется администратор"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "LDAP-сервер существует"
|
||||
@@ -101,11 +101,11 @@
|
||||
"Unknown modify rule %s.": "Неизвестное изменение правила %s."
|
||||
},
|
||||
"permission": {
|
||||
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
|
||||
"The permission: \\\"%s\\\" doesn't exist": "Разрешение: \\\"%s\\\" не существует"
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Неверный идентификатор приложения",
|
||||
"the provider: %s does not exist": "провайдер: %s не существует"
|
||||
"the provider: %s does not exist": "Провайдер: %s не существует"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар",
|
||||
@@ -115,7 +115,7 @@
|
||||
"Application %s not found": "Приложение %s не найдено"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "категория провайдера %s не является SAML"
|
||||
"provider %s's category is not SAML": "Категория провайдера %s не является SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v",
|
||||
@@ -148,7 +148,7 @@
|
||||
"verification": {
|
||||
"Invalid captcha provider.": "Недействительный поставщик CAPTCHA.",
|
||||
"Phone number is invalid in your region %s": "Номер телефона недействителен в вашем регионе %s",
|
||||
"The verification code has not been sent yet!": "The verification code has not been sent yet!",
|
||||
"The verification code has not been sent yet!": "Код проверки еще не отправлен!",
|
||||
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!",
|
||||
"Turing test failed.": "Тест Тьюринга не удался.",
|
||||
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
|
||||
@@ -156,8 +156,8 @@
|
||||
"Unknown type": "Неизвестный тип",
|
||||
"Wrong verification code!": "Неправильный код подтверждения!",
|
||||
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
|
||||
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s",
|
||||
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s",
|
||||
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "Пожалуйста, добавьте поставщика SMS в список \\\"Провайдеры\\\" для приложения: %s",
|
||||
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "Пожалуйста, добавьте поставщика электронной почты в список \\\"Провайдеры\\\" для приложения: %s",
|
||||
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
|
||||
},
|
||||
"webauthn": {
|
||||
|
@@ -95,6 +95,7 @@ type Application struct {
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||
IsShared bool `json:"isShared"`
|
||||
IpRestriction string `json:"ipRestriction"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@@ -108,6 +109,7 @@ type Application struct {
|
||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
||||
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
||||
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
@@ -721,8 +723,15 @@ func (application *Application) GetId() string {
|
||||
}
|
||||
|
||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app", ".chromiumapp.org"}, application.RedirectUris...)
|
||||
for _, targetUri := range redirectUris {
|
||||
isValid, err := util.IsValidOrigin(redirectUri)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if isValid {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, targetUri := range application.RedirectUris {
|
||||
targetUriRegex := regexp.MustCompile(targetUri)
|
||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||
return true
|
||||
|
@@ -539,6 +539,11 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
}
|
||||
}
|
||||
if oldUser.IpWhitelist != user.IpWhitelist {
|
||||
if err := CheckIpWhitelist(user.IpWhitelist, lang); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
100
object/check_ip.go
Normal file
100
object/check_ip.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
)
|
||||
|
||||
func CheckEntryIp(clientIp string, user *User, application *Application, organization *Organization, lang string) error {
|
||||
entryIp := net.ParseIP(clientIp)
|
||||
if entryIp == nil {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Failed to parse client IP: %s"), clientIp)
|
||||
} else if entryIp.IsLoopback() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if user != nil {
|
||||
err = isEntryIpAllowd(user.IpWhitelist, entryIp, lang)
|
||||
if err != nil {
|
||||
return fmt.Errorf(err.Error() + user.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if application != nil {
|
||||
err = isEntryIpAllowd(application.IpWhitelist, entryIp, lang)
|
||||
if err != nil {
|
||||
application.IpRestriction = err.Error() + application.Name
|
||||
return fmt.Errorf(err.Error() + application.Name)
|
||||
}
|
||||
|
||||
if organization == nil && application.OrganizationObj != nil {
|
||||
organization = application.OrganizationObj
|
||||
}
|
||||
}
|
||||
|
||||
if organization != nil {
|
||||
err = isEntryIpAllowd(organization.IpWhitelist, entryIp, lang)
|
||||
if err != nil {
|
||||
organization.IpRestriction = err.Error() + organization.Name
|
||||
return fmt.Errorf(err.Error() + organization.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEntryIpAllowd(ipWhitelistStr string, entryIp net.IP, lang string) error {
|
||||
if ipWhitelistStr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipWhitelist := strings.Split(ipWhitelistStr, ",")
|
||||
for _, ip := range ipWhitelist {
|
||||
_, ipNet, err := net.ParseCIDR(ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ipNet == nil {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:CIDR for IP: %s should not be empty"), entryIp.String())
|
||||
}
|
||||
|
||||
if ipNet.Contains(entryIp) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Your IP address: %s has been banned according to the configuration of: "), entryIp.String())
|
||||
}
|
||||
|
||||
func CheckIpWhitelist(ipWhitelistStr string, lang string) error {
|
||||
if ipWhitelistStr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipWhiteList := strings.Split(ipWhitelistStr, ",")
|
||||
for _, ip := range ipWhiteList {
|
||||
if _, _, err := net.ParseCIDR(ip); err != nil {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:%s does not meet the CIDR format requirements: %s"), ip, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -48,12 +48,16 @@ type InitData struct {
|
||||
Transactions []*Transaction `json:"transactions"`
|
||||
}
|
||||
|
||||
var initDataNewOnly bool
|
||||
|
||||
func InitFromFile() {
|
||||
initDataFile := conf.GetConfigString("initDataFile")
|
||||
if initDataFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
initDataNewOnly = conf.GetConfigBool("initDataNewOnly")
|
||||
|
||||
initData, err := readInitDataFromFile(initDataFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -269,6 +273,9 @@ func initDefinedOrganization(organization *Organization) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := deleteOrganization(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -295,6 +302,9 @@ func initDefinedApplication(application *Application) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := deleteApplication(application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -316,6 +326,9 @@ func initDefinedUser(user *User) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := deleteUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -342,6 +355,9 @@ func initDefinedCert(cert *Cert) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteCert(cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -364,6 +380,9 @@ func initDefinedLdap(ldap *Ldap) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteLdap(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -385,6 +404,9 @@ func initDefinedProvider(provider *Provider) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteProvider(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -406,6 +428,9 @@ func initDefinedModel(model *Model) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteModel(model)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -428,6 +453,9 @@ func initDefinedPermission(permission *Permission) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := deletePermission(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -450,6 +478,9 @@ func initDefinedPayment(payment *Payment) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeletePayment(payment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -472,6 +503,9 @@ func initDefinedProduct(product *Product) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteProduct(product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -494,6 +528,9 @@ func initDefinedResource(resource *Resource) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteResource(resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -516,6 +553,9 @@ func initDefinedRole(role *Role) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := deleteRole(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -538,6 +578,9 @@ func initDefinedSyncer(syncer *Syncer) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteSyncer(syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -560,6 +603,9 @@ func initDefinedToken(token *Token) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteToken(token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -582,6 +628,9 @@ func initDefinedWebhook(webhook *Webhook) {
|
||||
}
|
||||
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteWebhook(webhook)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -603,6 +652,9 @@ func initDefinedGroup(group *Group) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := deleteGroup(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -624,6 +676,9 @@ func initDefinedAdapter(adapter *Adapter) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteAdapter(adapter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -645,6 +700,9 @@ func initDefinedEnforcer(enforcer *Enforcer) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteEnforcer(enforcer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -666,6 +724,9 @@ func initDefinedPlan(plan *Plan) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeletePlan(plan)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -687,6 +748,9 @@ func initDefinedPricing(pricing *Pricing) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeletePricing(pricing)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -708,6 +772,9 @@ func initDefinedInvitation(invitation *Invitation) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteInvitation(invitation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -743,6 +810,9 @@ func initDefinedSubscription(subscription *Subscription) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteSubscription(subscription)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -764,6 +834,9 @@ func initDefinedTransaction(transaction *Transaction) {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
if initDataNewOnly {
|
||||
return
|
||||
}
|
||||
affected, err := DeleteTransaction(transaction)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@@ -71,11 +71,13 @@ type Organization struct {
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
||||
EnableTour bool `json:"enableTour"`
|
||||
IpRestriction string `json:"ipRestriction"`
|
||||
|
||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||
|
@@ -364,7 +364,7 @@ func GetAllActions(userId string) ([]string, error) {
|
||||
|
||||
res := []string{}
|
||||
for _, enforcer := range enforcers {
|
||||
items := enforcer.GetAllObjects()
|
||||
items := enforcer.GetAllActions()
|
||||
res = append(res, items...)
|
||||
}
|
||||
return res, nil
|
||||
|
@@ -332,6 +332,9 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return "", fmt.Errorf("The user: %s doesn't exist", util.GetId(application.Organization, token.User))
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
return &TokenError{
|
||||
|
@@ -206,6 +206,7 @@ type User struct {
|
||||
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
||||
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||
}
|
||||
|
||||
type Userinfo struct {
|
||||
@@ -696,7 +697,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
||||
"yammer", "yandex", "zoom", "custom", "need_update_password",
|
||||
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist",
|
||||
}
|
||||
}
|
||||
if isAdmin {
|
||||
@@ -815,6 +816,10 @@ func AddUser(user *User) (bool, error) {
|
||||
user.UpdateUserPassword(organization)
|
||||
}
|
||||
|
||||
if user.CreatedTime == "" {
|
||||
user.CreatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
err = user.UpdateUserHash()
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@@ -557,6 +557,14 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
}
|
||||
if oldUser.IpWhitelist != newUser.IpWhitelist {
|
||||
item := GetAccountItemByName("IP whitelist", organization)
|
||||
if item == nil {
|
||||
newUser.IpWhitelist = oldUser.IpWhitelist
|
||||
} else {
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
}
|
||||
|
||||
if oldUser.Balance != newUser.Balance {
|
||||
item := GetAccountItemByName("Balance", organization)
|
||||
|
@@ -16,11 +16,11 @@ package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -52,7 +52,13 @@ func CorsFilter(ctx *context.Context) {
|
||||
origin = ""
|
||||
}
|
||||
|
||||
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
||||
isValid, err := util.IsValidOrigin(origin)
|
||||
if err != nil {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
if isValid {
|
||||
setCorsHeaders(ctx, origin)
|
||||
return
|
||||
}
|
||||
|
21
util/log.go
21
util/log.go
@@ -29,16 +29,17 @@ func getIpInfo(clientIp string) string {
|
||||
}
|
||||
|
||||
ips := strings.Split(clientIp, ",")
|
||||
res := ""
|
||||
for i := range ips {
|
||||
ip := strings.TrimSpace(ips[i])
|
||||
ipstr := fmt.Sprintf("%s: %s", ip, "")
|
||||
if i != len(ips)-1 {
|
||||
res += ipstr + " -> "
|
||||
} else {
|
||||
res += ipstr
|
||||
}
|
||||
}
|
||||
res := strings.TrimSpace(ips[0])
|
||||
//res := ""
|
||||
//for i := range ips {
|
||||
// ip := strings.TrimSpace(ips[i])
|
||||
// ipstr := fmt.Sprintf("%s: %s", ip, "")
|
||||
// if i != len(ips)-1 {
|
||||
// res += ipstr + " -> "
|
||||
// } else {
|
||||
// res += ipstr
|
||||
// }
|
||||
//}
|
||||
|
||||
return res
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ package util
|
||||
import (
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -100,3 +101,21 @@ func GetCountryCode(prefix string, phone string) (string, error) {
|
||||
func FilterField(field string) bool {
|
||||
return ReFieldWhiteList.MatchString(field)
|
||||
}
|
||||
|
||||
func IsValidOrigin(origin string) (bool, error) {
|
||||
urlObj, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if urlObj == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
originHostOnly := ""
|
||||
if urlObj.Host != "" {
|
||||
originHostOnly = fmt.Sprintf("%s://%s", urlObj.Scheme, urlObj.Hostname())
|
||||
}
|
||||
|
||||
res := originHostOnly == "http://localhost" || originHostOnly == "https://localhost" || originHostOnly == "http://127.0.0.1" || originHostOnly == "http://casdoor-app" || strings.HasSuffix(originHostOnly, ".chromiumapp.org")
|
||||
return res, nil
|
||||
}
|
||||
|
@@ -598,6 +598,16 @@ class ApplicationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input placeholder = {this.state.application.organizationObj?.ipWhitelist} value={this.state.application.ipWhiteList} onChange={e => {
|
||||
this.updateApplicationField("ipWhitelist", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
||||
|
@@ -34,6 +34,7 @@ import PaymentResultPage from "./PaymentResultPage";
|
||||
import QrCodePage from "./QrCodePage";
|
||||
import CaptchaPage from "./CaptchaPage";
|
||||
import CustomHead from "./basic/CustomHead";
|
||||
import * as Util from "./auth/Util";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -94,6 +95,14 @@ class EntryPage extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
if (this.state.application?.ipRestriction) {
|
||||
return Util.renderMessageLarge(this, this.state.application.ipRestriction);
|
||||
}
|
||||
|
||||
if (this.state.application?.organizationObj?.ipRestriction) {
|
||||
return Util.renderMessageLarge(this, this.state.application.organizationObj.ipRestriction);
|
||||
}
|
||||
|
||||
const isDarkMode = this.props.themeAlgorithm.includes("dark");
|
||||
|
||||
return (
|
||||
|
@@ -452,6 +452,16 @@ class OrganizationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.ipWhitelist} onChange={e => {
|
||||
this.updateOrganizationField("ipWhitelist", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
||||
|
@@ -908,7 +908,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
|
@@ -1070,6 +1070,19 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "IP whitelist") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Input value={this.state.user.ipWhitelist} onChange={e => {
|
||||
this.updateUserField("ipWhitelist", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -227,6 +227,22 @@ class LoginPage extends React.Component {
|
||||
return "password";
|
||||
}
|
||||
|
||||
getCurrentLoginMethod() {
|
||||
if (this.state.loginMethod === "password") {
|
||||
return "Password";
|
||||
} else if (this.state.loginMethod?.includes("verificationCode")) {
|
||||
return "Verification code";
|
||||
} else if (this.state.loginMethod === "webAuthn") {
|
||||
return "WebAuthn";
|
||||
} else if (this.state.loginMethod === "ldap") {
|
||||
return "LDAP";
|
||||
} else if (this.state.loginMethod === "faceId") {
|
||||
return "Face ID";
|
||||
} else {
|
||||
return "Password";
|
||||
}
|
||||
}
|
||||
|
||||
getPlaceholder() {
|
||||
switch (this.state.loginMethod) {
|
||||
case "verificationCode": return i18next.t("login:Email or phone");
|
||||
@@ -262,17 +278,7 @@ class LoginPage extends React.Component {
|
||||
values["organization"] = this.getApplicationObj().organization;
|
||||
}
|
||||
|
||||
if (this.state.loginMethod === "password") {
|
||||
values["signinMethod"] = "Password";
|
||||
} else if (this.state.loginMethod?.includes("verificationCode")) {
|
||||
values["signinMethod"] = "Verification code";
|
||||
} else if (this.state.loginMethod === "webAuthn") {
|
||||
values["signinMethod"] = "WebAuthn";
|
||||
} else if (this.state.loginMethod === "ldap") {
|
||||
values["signinMethod"] = "LDAP";
|
||||
} else if (this.state.loginMethod === "faceId") {
|
||||
values["signinMethod"] = "Face ID";
|
||||
}
|
||||
values["signinMethod"] = this.getCurrentLoginMethod();
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
|
||||
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
||||
@@ -409,6 +415,7 @@ class LoginPage extends React.Component {
|
||||
if (this.state.type === "cas") {
|
||||
// CAS
|
||||
const casParams = Util.getCasParameters();
|
||||
values["signinMethod"] = this.getCurrentLoginMethod();
|
||||
values["type"] = this.state.type;
|
||||
AuthBackend.loginCas(values, casParams).then((res) => {
|
||||
const loginHandler = (res) => {
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
import CryptoJS from "crypto-js";
|
||||
import i18next from "i18next";
|
||||
import {Buffer} from "buffer";
|
||||
|
||||
export function getRandomKeyForObfuscator(obfuscatorType) {
|
||||
if (obfuscatorType === "DES") {
|
||||
|
@@ -972,7 +972,7 @@
|
||||
"Please input your affiliation!": "Пожалуйста, укажите свою принадлежность!",
|
||||
"Please input your display name!": "Пожалуйста, введите своё отображаемое имя!",
|
||||
"Please input your first name!": "Пожалуйста, введите свое имя!",
|
||||
"Please input your invitation code!": "Please input your invitation code!",
|
||||
"Please input your invitation code!": "Пожалуйста, введите код приглашения!",
|
||||
"Please input your last name!": "Введите свою фамилию!",
|
||||
"Please input your phone number!": "Пожалуйста, введите свой номер телефона!",
|
||||
"Please input your real name!": "Пожалуйста, введите своё настоящее имя!",
|
||||
@@ -1163,9 +1163,9 @@
|
||||
"MFA accounts": "MFA accounts",
|
||||
"Managed accounts": "Управляемые аккаунты",
|
||||
"Modify password...": "Изменить пароль...",
|
||||
"Multi-factor authentication": "Multi-factor authentication",
|
||||
"Need update password": "Need update password",
|
||||
"Need update password - Tooltip": "Force user update password after login",
|
||||
"Multi-factor authentication": "Многофакторная аутентификация",
|
||||
"Need update password": "Необходимо обновить пароль",
|
||||
"Need update password - Tooltip": "Заставить пользователя обновить пароль после входа в систему",
|
||||
"New Email": "Новое электронное письмо",
|
||||
"New Password": "Новый пароль",
|
||||
"New User": "Новый пользователь",
|
||||
@@ -1189,26 +1189,26 @@
|
||||
"Set password...": "Установить пароль...",
|
||||
"Tag": "Метка",
|
||||
"Tag - Tooltip": "Тег пользователя",
|
||||
"The password must contain at least one special character": "The password must contain at least one special character",
|
||||
"The password must contain at least one uppercase letter, one lowercase letter and one digit": "The password must contain at least one uppercase letter, one lowercase letter and one digit",
|
||||
"The password must have at least 6 characters": "The password must have at least 6 characters",
|
||||
"The password must have at least 8 characters": "The password must have at least 8 characters",
|
||||
"The password must not contain any repeated characters": "The password must not contain any repeated characters",
|
||||
"This field value doesn't match the pattern rule": "This field value doesn't match the pattern rule",
|
||||
"The password must contain at least one special character": "Пароль должен содержать хотя бы один специальный символ",
|
||||
"The password must contain at least one uppercase letter, one lowercase letter and one digit": "Пароль должен содержать как минимум одну заглавную букву, одну строчную букву и одну цифру",
|
||||
"The password must have at least 6 characters": "Пароль должен быть минимум 6 символов",
|
||||
"The password must have at least 8 characters": "Пароль должен быть минимум 8 символов",
|
||||
"The password must not contain any repeated characters": "Пароль не должен содержать повторяющиеся символы",
|
||||
"This field value doesn't match the pattern rule": "Значение поля не соответствует шаблону",
|
||||
"Title": "Заголовок",
|
||||
"Title - Tooltip": "Положение в аффилиации",
|
||||
"Two passwords you typed do not match.": "Два введенных вами пароля не совпадают.",
|
||||
"Unlink": "Отсоединить",
|
||||
"Upload (.xlsx)": "Загрузить (.xlsx)",
|
||||
"Upload ID card back picture": "Upload ID card back picture",
|
||||
"Upload ID card front picture": "Upload ID card front picture",
|
||||
"Upload ID card with person picture": "Upload ID card with person picture",
|
||||
"Upload ID card back picture": "Загрузите заднюю сторону удостоверения личности",
|
||||
"Upload ID card front picture": "Загрузите переднюю сторону удостоверения личности",
|
||||
"Upload ID card with person picture": "Загрузите удостоверение личности с фотографией",
|
||||
"Upload a photo": "Загрузить фото",
|
||||
"User Profile": "User Profile",
|
||||
"User Profile": "Профиль пользователя",
|
||||
"Values": "Значения",
|
||||
"Verification code sent": "Код подтверждения отправлен",
|
||||
"WebAuthn credentials": "WebAuthn удостоверения",
|
||||
"You have changed the username, please save your change first before modifying the password": "You have changed the username, please save your change first before modifying the password",
|
||||
"You have changed the username, please save your change first before modifying the password": "Имя было изменено, сохраните изменения перед сменой пароля",
|
||||
"input password": "введите пароль"
|
||||
},
|
||||
"verification": {
|
||||
|
@@ -104,6 +104,7 @@ class AccountTable extends React.Component {
|
||||
{name: "Is forbidden", label: i18next.t("user:Is forbidden")},
|
||||
{name: "Is deleted", label: i18next.t("user:Is deleted")},
|
||||
{name: "Need update password", label: i18next.t("user:Need update password")},
|
||||
{name: "IP whitelist", label: i18next.t("general:IP whitelist")},
|
||||
{name: "Multi-factor authentication", label: i18next.t("user:Multi-factor authentication")},
|
||||
{name: "WebAuthn credentials", label: i18next.t("user:WebAuthn credentials")},
|
||||
{name: "Managed accounts", label: i18next.t("user:Managed accounts")},
|
||||
|
Reference in New Issue
Block a user