mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-11 18:14:15 +01:00
Add password requirement info on error (#9074)
* Add password requirement info on error * Move BuildComplexityError to the password pkg * Unexport complexity type * Fix extra line * Update modules/password/password.go Co-Authored-By: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
eb0359cad4
commit
c57edb6c7b
9 changed files with 72 additions and 24 deletions
|
@ -5,24 +5,44 @@
|
||||||
package password
|
package password
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// complexity contains information about a particular kind of password complexity
|
||||||
|
type complexity struct {
|
||||||
|
ValidChars string
|
||||||
|
TrNameOne string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
matchComplexityOnce sync.Once
|
matchComplexityOnce sync.Once
|
||||||
validChars string
|
validChars string
|
||||||
requiredChars []string
|
requiredList []complexity
|
||||||
|
|
||||||
charComplexities = map[string]string{
|
charComplexities = map[string]complexity{
|
||||||
"lower": `abcdefghijklmnopqrstuvwxyz`,
|
"lower": {
|
||||||
"upper": `ABCDEFGHIJKLMNOPQRSTUVWXYZ`,
|
`abcdefghijklmnopqrstuvwxyz`,
|
||||||
"digit": `0123456789`,
|
"form.password_lowercase_one",
|
||||||
"spec": ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`",
|
},
|
||||||
|
"upper": {
|
||||||
|
`ABCDEFGHIJKLMNOPQRSTUVWXYZ`,
|
||||||
|
"form.password_uppercase_one",
|
||||||
|
},
|
||||||
|
"digit": {
|
||||||
|
`0123456789`,
|
||||||
|
"form.password_digit_one",
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`",
|
||||||
|
"form.password_special_one",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,22 +56,22 @@ func NewComplexity() {
|
||||||
func setupComplexity(values []string) {
|
func setupComplexity(values []string) {
|
||||||
if len(values) != 1 || values[0] != "off" {
|
if len(values) != 1 || values[0] != "off" {
|
||||||
for _, val := range values {
|
for _, val := range values {
|
||||||
if chars, ok := charComplexities[val]; ok {
|
if complex, ok := charComplexities[val]; ok {
|
||||||
validChars += chars
|
validChars += complex.ValidChars
|
||||||
requiredChars = append(requiredChars, chars)
|
requiredList = append(requiredList, complex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(requiredChars) == 0 {
|
if len(requiredList) == 0 {
|
||||||
// No valid character classes found; use all classes as default
|
// No valid character classes found; use all classes as default
|
||||||
for _, chars := range charComplexities {
|
for _, complex := range charComplexities {
|
||||||
validChars += chars
|
validChars += complex.ValidChars
|
||||||
requiredChars = append(requiredChars, chars)
|
requiredList = append(requiredList, complex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if validChars == "" {
|
if validChars == "" {
|
||||||
// No complexities to check; provide a sensible default for password generation
|
// No complexities to check; provide a sensible default for password generation
|
||||||
validChars = charComplexities["lower"] + charComplexities["upper"] + charComplexities["digit"]
|
validChars = charComplexities["lower"].ValidChars + charComplexities["upper"].ValidChars + charComplexities["digit"].ValidChars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +79,8 @@ func setupComplexity(values []string) {
|
||||||
func IsComplexEnough(pwd string) bool {
|
func IsComplexEnough(pwd string) bool {
|
||||||
NewComplexity()
|
NewComplexity()
|
||||||
if len(validChars) > 0 {
|
if len(validChars) > 0 {
|
||||||
for _, req := range requiredChars {
|
for _, req := range requiredList {
|
||||||
if !strings.ContainsAny(req, pwd) {
|
if !strings.ContainsAny(req.ValidChars, pwd) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,3 +106,17 @@ func Generate(n int) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildComplexityError builds the error message when password complexity checks fail
|
||||||
|
func BuildComplexityError(ctx *context.Context) string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(ctx.Tr("form.password_complexity"))
|
||||||
|
buffer.WriteString("<ul>")
|
||||||
|
for _, c := range requiredList {
|
||||||
|
buffer.WriteString("<li>")
|
||||||
|
buffer.WriteString(ctx.Tr(c.TrNameOne))
|
||||||
|
buffer.WriteString("</li>")
|
||||||
|
}
|
||||||
|
buffer.WriteString("</ul>")
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ func TestComplexity_IsComplexEnough(t *testing.T) {
|
||||||
truevalues []string
|
truevalues []string
|
||||||
falsevalues []string
|
falsevalues []string
|
||||||
}{
|
}{
|
||||||
|
{[]string{"off"}, []string{"1", "-", "a", "A", "ñ", "日本語"}, []string{}},
|
||||||
{[]string{"lower"}, []string{"abc", "abc!"}, []string{"ABC", "123", "=!$", ""}},
|
{[]string{"lower"}, []string{"abc", "abc!"}, []string{"ABC", "123", "=!$", ""}},
|
||||||
{[]string{"upper"}, []string{"ABC"}, []string{"abc", "123", "=!$", "abc!", ""}},
|
{[]string{"upper"}, []string{"ABC"}, []string{"abc", "123", "=!$", "abc!", ""}},
|
||||||
{[]string{"digit"}, []string{"123"}, []string{"abc", "ABC", "=!$", "abc!", ""}},
|
{[]string{"digit"}, []string{"123"}, []string{"abc", "ABC", "=!$", "abc!", ""}},
|
||||||
|
@ -25,6 +26,7 @@ func TestComplexity_IsComplexEnough(t *testing.T) {
|
||||||
{[]string{"off"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}, nil},
|
{[]string{"off"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}, nil},
|
||||||
{[]string{"lower", "spec"}, []string{"abc!"}, []string{"abc", "ABC", "123", "=!$", "abcABC123", ""}},
|
{[]string{"lower", "spec"}, []string{"abc!"}, []string{"abc", "ABC", "123", "=!$", "abcABC123", ""}},
|
||||||
{[]string{"lower", "upper", "digit"}, []string{"abcABC123"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}},
|
{[]string{"lower", "upper", "digit"}, []string{"abcABC123"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}},
|
||||||
|
{[]string{""}, []string{"abC=1", "abc!9D"}, []string{"ABC", "123", "=!$", ""}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testlist {
|
for _, test := range testlist {
|
||||||
|
@ -70,6 +72,6 @@ func TestComplexity_Generate(t *testing.T) {
|
||||||
func testComplextity(values []string) {
|
func testComplextity(values []string) {
|
||||||
// Cleanup previous values
|
// Cleanup previous values
|
||||||
validChars = ""
|
validChars = ""
|
||||||
requiredChars = make([]string, 0, len(values))
|
requiredList = make([]complexity, 0, len(values))
|
||||||
setupComplexity(values)
|
setupComplexity(values)
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,7 +328,11 @@ team_no_units_error = Allow access to at least one repository section.
|
||||||
email_been_used = The email address is already used.
|
email_been_used = The email address is already used.
|
||||||
openid_been_used = The OpenID address '%s' is already used.
|
openid_been_used = The OpenID address '%s' is already used.
|
||||||
username_password_incorrect = Username or password is incorrect.
|
username_password_incorrect = Username or password is incorrect.
|
||||||
password_complexity = Password does not pass complexity requirements.
|
password_complexity = Password does not pass complexity requirements:
|
||||||
|
password_lowercase_one = At least one lowercase character
|
||||||
|
password_uppercase_one = At least one uppercase character
|
||||||
|
password_digit_one = At least one digit
|
||||||
|
password_special_one = At least one special character (punctuation, brackets, quotes, etc.)
|
||||||
enterred_invalid_repo_name = The repository name you entered is incorrect.
|
enterred_invalid_repo_name = The repository name you entered is incorrect.
|
||||||
enterred_invalid_owner_name = The new owner name is not valid.
|
enterred_invalid_owner_name = The new owner name is not valid.
|
||||||
enterred_invalid_password = The password you entered is incorrect.
|
enterred_invalid_password = The password you entered is incorrect.
|
||||||
|
|
|
@ -113,6 +113,7 @@ a{cursor:pointer}
|
||||||
.ui .text.nopadding{padding:0}
|
.ui .text.nopadding{padding:0}
|
||||||
.ui .text.nomargin{margin:0}
|
.ui .text.nomargin{margin:0}
|
||||||
.ui .message{text-align:center}
|
.ui .message{text-align:center}
|
||||||
|
.ui .message>ul{margin-left:auto;margin-right:auto;display:table;text-align:left}
|
||||||
.ui.bottom.attached.message{font-weight:700;text-align:left;color:#000}
|
.ui.bottom.attached.message{font-weight:700;text-align:left;color:#000}
|
||||||
.ui.bottom.attached.message .pull-right{color:#000}
|
.ui.bottom.attached.message .pull-right{color:#000}
|
||||||
.ui.bottom.attached.message .pull-right>span,.ui.bottom.attached.message>span{color:#21ba45}
|
.ui.bottom.attached.message .pull-right>span,.ui.bottom.attached.message>span{color:#21ba45}
|
||||||
|
|
|
@ -96,7 +96,7 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
|
||||||
}
|
}
|
||||||
if u.LoginType == models.LoginPlain {
|
if u.LoginType == models.LoginPlain {
|
||||||
if !password.IsComplexEnough(form.Password) {
|
if !password.IsComplexEnough(form.Password) {
|
||||||
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form)
|
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplUserNew, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u.MustChangePassword = form.MustChangePassword
|
u.MustChangePassword = form.MustChangePassword
|
||||||
|
@ -208,7 +208,7 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !password.IsComplexEnough(form.Password) {
|
if !password.IsComplexEnough(form.Password) {
|
||||||
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserEdit, &form)
|
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplUserEdit, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u.HashPassword(form.Password)
|
u.HashPassword(form.Password)
|
||||||
|
|
|
@ -1072,7 +1072,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
|
||||||
}
|
}
|
||||||
if !password.IsComplexEnough(form.Password) {
|
if !password.IsComplexEnough(form.Password) {
|
||||||
ctx.Data["Err_Password"] = true
|
ctx.Data["Err_Password"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplSignUp, &form)
|
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplSignUp, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1343,7 +1343,7 @@ func ResetPasswdPost(ctx *context.Context) {
|
||||||
} else if !password.IsComplexEnough(passwd) {
|
} else if !password.IsComplexEnough(passwd) {
|
||||||
ctx.Data["IsResetForm"] = true
|
ctx.Data["IsResetForm"] = true
|
||||||
ctx.Data["Err_Password"] = true
|
ctx.Data["Err_Password"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplResetPassword, nil)
|
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplResetPassword, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
|
||||||
} else if form.Password != form.Retype {
|
} else if form.Password != form.Retype {
|
||||||
ctx.Flash.Error(ctx.Tr("form.password_not_match"))
|
ctx.Flash.Error(ctx.Tr("form.password_not_match"))
|
||||||
} else if !password.IsComplexEnough(form.Password) {
|
} else if !password.IsComplexEnough(form.Password) {
|
||||||
ctx.Flash.Error(ctx.Tr("form.password_complexity"))
|
ctx.Flash.Error(password.BuildComplexityError(ctx))
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
if ctx.User.Salt, err = models.GetUserSalt(); err != nil {
|
if ctx.User.Salt, err = models.GetUserSalt(); err != nil {
|
||||||
|
|
|
@ -91,7 +91,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
Retype: req.Retype,
|
Retype: req.Retype,
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.EqualValues(t, req.Message, ctx.Flash.ErrorMsg)
|
assert.Contains(t, ctx.Flash.ErrorMsg, req.Message)
|
||||||
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -471,6 +471,13 @@ code,
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message > ul {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
display: table;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
&.bottom.attached.message {
|
&.bottom.attached.message {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
Loading…
Reference in a new issue