From dd136858f1ea40ad3c94191d647487fa4f31926c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.0. Signed-off-by: Daniel Baumann --- modules/auth/password/password.go | 136 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 modules/auth/password/password.go (limited to 'modules/auth/password/password.go') diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go new file mode 100644 index 0000000..85f9780 --- /dev/null +++ b/modules/auth/password/password.go @@ -0,0 +1,136 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package password + +import ( + "bytes" + "context" + "crypto/rand" + "errors" + "html/template" + "math/big" + "strings" + "sync" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" +) + +var ( + ErrComplexity = errors.New("password not complex enough") + ErrMinLength = errors.New("password not long enough") +) + +// complexity contains information about a particular kind of password complexity +type complexity struct { + ValidChars string + TrNameOne string +} + +var ( + matchComplexityOnce sync.Once + validChars string + requiredList []complexity + + charComplexities = map[string]complexity{ + "lower": { + `abcdefghijklmnopqrstuvwxyz`, + "form.password_lowercase_one", + }, + "upper": { + `ABCDEFGHIJKLMNOPQRSTUVWXYZ`, + "form.password_uppercase_one", + }, + "digit": { + `0123456789`, + "form.password_digit_one", + }, + "spec": { + ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`", + "form.password_special_one", + }, + } +) + +// NewComplexity for preparation +func NewComplexity() { + matchComplexityOnce.Do(func() { + setupComplexity(setting.PasswordComplexity) + }) +} + +func setupComplexity(values []string) { + if len(values) != 1 || values[0] != "off" { + for _, val := range values { + if complexity, ok := charComplexities[val]; ok { + validChars += complexity.ValidChars + requiredList = append(requiredList, complexity) + } + } + if len(requiredList) == 0 { + // No valid character classes found; use all classes as default + for _, complexity := range charComplexities { + validChars += complexity.ValidChars + requiredList = append(requiredList, complexity) + } + } + } + if validChars == "" { + // No complexities to check; provide a sensible default for password generation + validChars = charComplexities["lower"].ValidChars + charComplexities["upper"].ValidChars + charComplexities["digit"].ValidChars + } +} + +// IsComplexEnough return True if password meets complexity settings +func IsComplexEnough(pwd string) bool { + NewComplexity() + if len(validChars) > 0 { + for _, req := range requiredList { + if !strings.ContainsAny(req.ValidChars, pwd) { + return false + } + } + } + return true +} + +// Generate a random password +func Generate(n int) (string, error) { + NewComplexity() + buffer := make([]byte, n) + max := big.NewInt(int64(len(validChars))) + for { + for j := 0; j < n; j++ { + rnd, err := rand.Int(rand.Reader, max) + if err != nil { + return "", err + } + buffer[j] = validChars[rnd.Int64()] + } + + if err := IsPwned(context.Background(), string(buffer)); err != nil { + if errors.Is(err, ErrIsPwned) { + continue + } + return "", err + } + if IsComplexEnough(string(buffer)) && string(buffer[0]) != " " && string(buffer[n-1]) != " " { + return string(buffer), nil + } + } +} + +// BuildComplexityError builds the error message when password complexity checks fail +func BuildComplexityError(locale translation.Locale) template.HTML { + var buffer bytes.Buffer + buffer.WriteString(locale.TrString("form.password_complexity")) + buffer.WriteString("") + return template.HTML(buffer.String()) +} -- cgit v1.2.3