From 0f57ac297b4bd1b19fb97641eba7e8f32aa98434 Mon Sep 17 00:00:00 2001 From: leoil <31148569+leoil@users.noreply.github.com> Date: Sat, 17 Jun 2023 00:01:20 +0800 Subject: [PATCH] ci: add password complexity options to organization edit page (#1949) * Support uploading roles and permissions via xlsx file. * Template xlsx file for uploading users and permissions. * reformat according to gofumpt. * fix typo. * add password complexity options to organization edit page. * add password complexity options to organization edit page. * Fixed Typos. * Fixed Typos. * feat:add password complexity options to organization edit page * Auto generate i18n fields. * Refactor code according to instructions * Support autocheck passwd complexity in frontend when setting passwd in user edit page. * feat:Backend Support for password validation in signup and forget page. * feat:Frontend Support for password validation in signup and forget page. * Add default password complex option & Update historical empty filed with default option. * Migrator for field `password_complex_options` in org table. * feat: support frontend password complex option check in user_edit/forget/signup page. * frontend update for user edit page * update i18n file --------- Co-authored-by: hsluoyz --- controllers/account.go | 7 ++ controllers/user.go | 6 ++ init_data.json.template | 1 + object/check.go | 10 +++ object/check_password_complexity.go | 98 +++++++++++++++++++++++++ object/init.go | 1 + object/organization.go | 1 + web/src/OrganizationEditPage.js | 23 ++++++ web/src/OrganizationListPage.js | 1 + web/src/auth/ForgetPage.js | 14 +++- web/src/auth/SignupPage.js | 12 +++- web/src/common/PasswordChecker.js | 82 +++++++++++++++++++++ web/src/common/modal/PasswordModal.js | 100 +++++++++++++++++++++++--- web/src/locales/de/data.json | 8 ++- web/src/locales/en/data.json | 8 ++- web/src/locales/es/data.json | 8 ++- web/src/locales/fr/data.json | 8 ++- web/src/locales/id/data.json | 8 ++- web/src/locales/ja/data.json | 8 ++- web/src/locales/ko/data.json | 8 ++- web/src/locales/pt/data.json | 8 ++- web/src/locales/ru/data.json | 8 ++- web/src/locales/vi/data.json | 8 ++- web/src/locales/zh/data.json | 8 ++- 24 files changed, 420 insertions(+), 24 deletions(-) create mode 100644 object/check_password_complexity.go create mode 100644 web/src/common/PasswordChecker.js diff --git a/controllers/account.go b/controllers/account.go index 5e045507..29bfc109 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -140,6 +140,13 @@ func (c *ApiController) Signup() { username = id } + password := authForm.Password + msg = object.CheckPasswordComplexityByOrg(organization, password) + if msg != "" { + c.ResponseError(msg) + return + } + initScore, err := organization.GetInitScore() if err != nil { c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error()) diff --git a/controllers/user.go b/controllers/user.go index cce3024a..46b5b4ee 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -448,6 +448,12 @@ func (c *ApiController) SetPassword() { } } + msg := object.CheckPasswordComplexity(targetUser, newPassword) + if msg != "" { + c.ResponseError(msg) + return + } + targetUser.Password = newPassword _, err = object.SetUserField(targetUser, "password", targetUser.Password) if err != nil { diff --git a/init_data.json.template b/init_data.json.template index 0e197ec9..667471c9 100644 --- a/init_data.json.template +++ b/init_data.json.template @@ -8,6 +8,7 @@ "favicon": "", "passwordType": "plain", "passwordSalt": "", + "passwordOptions": ["AtLeast6"], "countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"], "defaultAvatar": "", "defaultApplication": "", diff --git a/object/check.go b/object/check.go index 767c2ab3..fe258bc8 100644 --- a/object/check.go +++ b/object/check.go @@ -203,6 +203,16 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st } } +func CheckPasswordComplexityByOrg(organization *Organization, password string) string { + errorMsg := checkPasswordComplexity(password, organization.PasswordOptions) + return errorMsg +} + +func CheckPasswordComplexity(user *User, password string) string { + organization, _ := GetOrganizationByUser(user) + return CheckPasswordComplexityByOrg(organization, password) +} + func checkLdapUserPassword(user *User, password string, lang string) string { ldaps, err := GetLdaps(user.Owner) if err != nil { diff --git a/object/check_password_complexity.go b/object/check_password_complexity.go new file mode 100644 index 00000000..859b690a --- /dev/null +++ b/object/check_password_complexity.go @@ -0,0 +1,98 @@ +// Copyright 2023 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 ( + "regexp" +) + +type ValidatorFunc func(password string) string + +var ( + regexLowerCase = regexp.MustCompile(`[a-z]`) + regexUpperCase = regexp.MustCompile(`[A-Z]`) + regexDigit = regexp.MustCompile(`\d`) + regexSpecial = regexp.MustCompile(`[!@#$%^&*]`) +) + +func isValidOption_AtLeast6(password string) string { + if len(password) < 6 { + return "The password must have at least 6 characters" + } + return "" +} + +func isValidOption_AtLeast8(password string) string { + if len(password) < 8 { + return "The password must have at least 8 characters" + } + return "" +} + +func isValidOption_Aa123(password string) string { + hasLowerCase := regexLowerCase.MatchString(password) + hasUpperCase := regexUpperCase.MatchString(password) + hasDigit := regexDigit.MatchString(password) + + if !hasLowerCase || !hasUpperCase || !hasDigit { + return "The password must contain at least one uppercase letter, one lowercase letter and one digit" + } + return "" +} + +func isValidOption_SpecialChar(password string) string { + if !regexSpecial.MatchString(password) { + return "The password must contain at least one special character" + } + return "" +} + +func isValidOption_NoRepeat(password string) string { + for i := 0; i < len(password)-1; i++ { + if password[i] == password[i+1] { + return "The password must not contain any repeated characters" + } + } + return "" +} + +func checkPasswordComplexity(password string, options []string) string { + if len(password) == 0 { + return "Please input your password!" + } + + if len(options) == 0 { + options = []string{"AtLeast6"} + } + + checkers := map[string]ValidatorFunc{ + "AtLeast6": isValidOption_AtLeast6, + "AtLeast8": isValidOption_AtLeast8, + "Aa123": isValidOption_Aa123, + "SpecialChar": isValidOption_SpecialChar, + "NoRepeat": isValidOption_NoRepeat, + } + + for _, option := range options { + checkerFunc, ok := checkers[option] + if ok { + errorMsg := checkerFunc(password) + if errorMsg != "" { + return errorMsg + } + } + } + return "" +} diff --git a/object/init.go b/object/init.go index 7d864e35..79345184 100644 --- a/object/init.go +++ b/object/init.go @@ -92,6 +92,7 @@ func initBuiltInOrganization() bool { WebsiteUrl: "https://example.com", Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")), PasswordType: "plain", + PasswordOptions: []string{"AtLeast6"}, CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"}, DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), Tags: []string{}, diff --git a/object/organization.go b/object/organization.go index ec7f791a..10970b46 100644 --- a/object/organization.go +++ b/object/organization.go @@ -56,6 +56,7 @@ type Organization struct { Favicon string `xorm:"varchar(100)" json:"favicon"` PasswordType string `xorm:"varchar(100)" json:"passwordType"` PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` + PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"` CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"` DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` diff --git a/web/src/OrganizationEditPage.js b/web/src/OrganizationEditPage.js index 1122c90c..52e24b4f 100644 --- a/web/src/OrganizationEditPage.js +++ b/web/src/OrganizationEditPage.js @@ -193,6 +193,29 @@ class OrganizationEditPage extends React.Component { }} /> + + + {Setting.getLabel(i18next.t("general:Password complexity options"), i18next.t("general:Password complexity options - Tooltip"))} : + + +