Skip to content

Commit c57edb6

Browse files
guillep2klafriks
authored andcommitted
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 <[email protected]>
1 parent eb0359c commit c57edb6

File tree

9 files changed

+72
-24
lines changed

9 files changed

+72
-24
lines changed

modules/password/password.go

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,44 @@
55
package password
66

77
import (
8+
"bytes"
89
"crypto/rand"
910
"math/big"
1011
"strings"
1112
"sync"
1213

14+
"code.gitea.io/gitea/modules/context"
1315
"code.gitea.io/gitea/modules/setting"
1416
)
1517

18+
// complexity contains information about a particular kind of password complexity
19+
type complexity struct {
20+
ValidChars string
21+
TrNameOne string
22+
}
23+
1624
var (
1725
matchComplexityOnce sync.Once
1826
validChars string
19-
requiredChars []string
27+
requiredList []complexity
2028

21-
charComplexities = map[string]string{
22-
"lower": `abcdefghijklmnopqrstuvwxyz`,
23-
"upper": `ABCDEFGHIJKLMNOPQRSTUVWXYZ`,
24-
"digit": `0123456789`,
25-
"spec": ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`",
29+
charComplexities = map[string]complexity{
30+
"lower": {
31+
`abcdefghijklmnopqrstuvwxyz`,
32+
"form.password_lowercase_one",
33+
},
34+
"upper": {
35+
`ABCDEFGHIJKLMNOPQRSTUVWXYZ`,
36+
"form.password_uppercase_one",
37+
},
38+
"digit": {
39+
`0123456789`,
40+
"form.password_digit_one",
41+
},
42+
"spec": {
43+
` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`",
44+
"form.password_special_one",
45+
},
2646
}
2747
)
2848

@@ -36,31 +56,31 @@ func NewComplexity() {
3656
func setupComplexity(values []string) {
3757
if len(values) != 1 || values[0] != "off" {
3858
for _, val := range values {
39-
if chars, ok := charComplexities[val]; ok {
40-
validChars += chars
41-
requiredChars = append(requiredChars, chars)
59+
if complex, ok := charComplexities[val]; ok {
60+
validChars += complex.ValidChars
61+
requiredList = append(requiredList, complex)
4262
}
4363
}
44-
if len(requiredChars) == 0 {
64+
if len(requiredList) == 0 {
4565
// No valid character classes found; use all classes as default
46-
for _, chars := range charComplexities {
47-
validChars += chars
48-
requiredChars = append(requiredChars, chars)
66+
for _, complex := range charComplexities {
67+
validChars += complex.ValidChars
68+
requiredList = append(requiredList, complex)
4969
}
5070
}
5171
}
5272
if validChars == "" {
5373
// No complexities to check; provide a sensible default for password generation
54-
validChars = charComplexities["lower"] + charComplexities["upper"] + charComplexities["digit"]
74+
validChars = charComplexities["lower"].ValidChars + charComplexities["upper"].ValidChars + charComplexities["digit"].ValidChars
5575
}
5676
}
5777

5878
// IsComplexEnough return True if password meets complexity settings
5979
func IsComplexEnough(pwd string) bool {
6080
NewComplexity()
6181
if len(validChars) > 0 {
62-
for _, req := range requiredChars {
63-
if !strings.ContainsAny(req, pwd) {
82+
for _, req := range requiredList {
83+
if !strings.ContainsAny(req.ValidChars, pwd) {
6484
return false
6585
}
6686
}
@@ -86,3 +106,17 @@ func Generate(n int) (string, error) {
86106
}
87107
}
88108
}
109+
110+
// BuildComplexityError builds the error message when password complexity checks fail
111+
func BuildComplexityError(ctx *context.Context) string {
112+
var buffer bytes.Buffer
113+
buffer.WriteString(ctx.Tr("form.password_complexity"))
114+
buffer.WriteString("<ul>")
115+
for _, c := range requiredList {
116+
buffer.WriteString("<li>")
117+
buffer.WriteString(ctx.Tr(c.TrNameOne))
118+
buffer.WriteString("</li>")
119+
}
120+
buffer.WriteString("</ul>")
121+
return buffer.String()
122+
}

modules/password/password_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ func TestComplexity_IsComplexEnough(t *testing.T) {
1818
truevalues []string
1919
falsevalues []string
2020
}{
21+
{[]string{"off"}, []string{"1", "-", "a", "A", "ñ", "日本語"}, []string{}},
2122
{[]string{"lower"}, []string{"abc", "abc!"}, []string{"ABC", "123", "=!$", ""}},
2223
{[]string{"upper"}, []string{"ABC"}, []string{"abc", "123", "=!$", "abc!", ""}},
2324
{[]string{"digit"}, []string{"123"}, []string{"abc", "ABC", "=!$", "abc!", ""}},
2425
{[]string{"spec"}, []string{"=!$", "abc!"}, []string{"abc", "ABC", "123", ""}},
2526
{[]string{"off"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}, nil},
2627
{[]string{"lower", "spec"}, []string{"abc!"}, []string{"abc", "ABC", "123", "=!$", "abcABC123", ""}},
2728
{[]string{"lower", "upper", "digit"}, []string{"abcABC123"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}},
29+
{[]string{""}, []string{"abC=1", "abc!9D"}, []string{"ABC", "123", "=!$", ""}},
2830
}
2931

3032
for _, test := range testlist {
@@ -70,6 +72,6 @@ func TestComplexity_Generate(t *testing.T) {
7072
func testComplextity(values []string) {
7173
// Cleanup previous values
7274
validChars = ""
73-
requiredChars = make([]string, 0, len(values))
75+
requiredList = make([]complexity, 0, len(values))
7476
setupComplexity(values)
7577
}

options/locale/locale_en-US.ini

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,11 @@ team_no_units_error = Allow access to at least one repository section.
328328
email_been_used = The email address is already used.
329329
openid_been_used = The OpenID address '%s' is already used.
330330
username_password_incorrect = Username or password is incorrect.
331-
password_complexity = Password does not pass complexity requirements.
331+
password_complexity = Password does not pass complexity requirements:
332+
password_lowercase_one = At least one lowercase character
333+
password_uppercase_one = At least one uppercase character
334+
password_digit_one = At least one digit
335+
password_special_one = At least one special character (punctuation, brackets, quotes, etc.)
332336
enterred_invalid_repo_name = The repository name you entered is incorrect.
333337
enterred_invalid_owner_name = The new owner name is not valid.
334338
enterred_invalid_password = The password you entered is incorrect.

public/css/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ a{cursor:pointer}
113113
.ui .text.nopadding{padding:0}
114114
.ui .text.nomargin{margin:0}
115115
.ui .message{text-align:center}
116+
.ui .message>ul{margin-left:auto;margin-right:auto;display:table;text-align:left}
116117
.ui.bottom.attached.message{font-weight:700;text-align:left;color:#000}
117118
.ui.bottom.attached.message .pull-right{color:#000}
118119
.ui.bottom.attached.message .pull-right>span,.ui.bottom.attached.message>span{color:#21ba45}

routers/admin/users.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
9696
}
9797
if u.LoginType == models.LoginPlain {
9898
if !password.IsComplexEnough(form.Password) {
99-
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form)
99+
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplUserNew, &form)
100100
return
101101
}
102102
u.MustChangePassword = form.MustChangePassword
@@ -208,7 +208,7 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
208208
return
209209
}
210210
if !password.IsComplexEnough(form.Password) {
211-
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserEdit, &form)
211+
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplUserEdit, &form)
212212
return
213213
}
214214
u.HashPassword(form.Password)

routers/user/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
10721072
}
10731073
if !password.IsComplexEnough(form.Password) {
10741074
ctx.Data["Err_Password"] = true
1075-
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplSignUp, &form)
1075+
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplSignUp, &form)
10761076
return
10771077
}
10781078

@@ -1343,7 +1343,7 @@ func ResetPasswdPost(ctx *context.Context) {
13431343
} else if !password.IsComplexEnough(passwd) {
13441344
ctx.Data["IsResetForm"] = true
13451345
ctx.Data["Err_Password"] = true
1346-
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplResetPassword, nil)
1346+
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplResetPassword, nil)
13471347
return
13481348
}
13491349

routers/user/setting/account.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
5353
} else if form.Password != form.Retype {
5454
ctx.Flash.Error(ctx.Tr("form.password_not_match"))
5555
} else if !password.IsComplexEnough(form.Password) {
56-
ctx.Flash.Error(ctx.Tr("form.password_complexity"))
56+
ctx.Flash.Error(password.BuildComplexityError(ctx))
5757
} else {
5858
var err error
5959
if ctx.User.Salt, err = models.GetUserSalt(); err != nil {

routers/user/setting/account_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func TestChangePassword(t *testing.T) {
9191
Retype: req.Retype,
9292
})
9393

94-
assert.EqualValues(t, req.Message, ctx.Flash.ErrorMsg)
94+
assert.Contains(t, ctx.Flash.ErrorMsg, req.Message)
9595
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
9696
}
9797
}

web_src/less/_base.less

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,13 @@ code,
471471
text-align: center;
472472
}
473473

474+
.message > ul {
475+
margin-left: auto;
476+
margin-right: auto;
477+
display: table;
478+
text-align: left;
479+
}
480+
474481
&.bottom.attached.message {
475482
font-weight: bold;
476483
text-align: left;

0 commit comments

Comments
 (0)