From 461cb21b3e3e87bf4580af0d565309af2bed4f34 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 22 Oct 2026 12:00:18 +0200 Subject: [PATCH] fix(cabana): enforce the 4-character password minimum on the Administrators form The form's min:4 rule never applied: the model validator skips string min and max. storePassword now refuses a typed password under 4 characters with a 422 on the field, next to the 72-byte check (D-11). Found by TestAdministratorsPasswordAndIdentity. --- modules/cabana/backend_users_admin.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/cabana/backend_users_admin.go b/modules/cabana/backend_users_admin.go index bbfa5eda..9b31014f 100644 --- a/modules/cabana/backend_users_admin.go +++ b/modules/cabana/backend_users_admin.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "time" + "unicode/utf8" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/bouncer" @@ -841,13 +842,24 @@ func submittedPassword(ctx context.Context) string { return plain } +// adminPasswordMinChars is the shortest password the Administrators form +// accepts, in characters (D-11). +const adminPasswordMinChars = 4 + // storePassword hashes a typed password onto user and reports whether one -// was typed. A password over bcrypt's 72-byte limit is a 422 on the field. +// was typed. A password under adminPasswordMinChars characters or over +// bcrypt's 72-byte limit is a 422 on the field. The length is checked here +// because the form's min:4 rule is a string rule, which the model validator +// does not apply. func (c usersAdmin) storePassword(ctx context.Context, user *BackendUser) (bool, error) { plain := submittedPassword(ctx) if plain == "" { return false, nil } + if utf8.RuneCountInString(plain) < adminPasswordMinChars { + message := accountMessage(ctx, c.translator(), "min.string", "password", map[string]string{"min": strconv.Itoa(adminPasswordMinChars)}) + return false, &ValidationError{Details: map[string]any{"password": []string{message}}} + } if len(plain) > passwordMaxBytes { message := accountMessage(ctx, c.translator(), "max.string", "password", map[string]string{"max": strconv.Itoa(passwordMaxBytes)}) return false, &ValidationError{Details: map[string]any{"password": []string{message}}}