code.gitea.io/gitea@v1.19.3/modules/auth/password/password.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package password 5 6 import ( 7 "bytes" 8 goContext "context" 9 "crypto/rand" 10 "math/big" 11 "strings" 12 "sync" 13 14 "code.gitea.io/gitea/modules/setting" 15 "code.gitea.io/gitea/modules/translation" 16 ) 17 18 // complexity contains information about a particular kind of password complexity 19 type complexity struct { 20 ValidChars string 21 TrNameOne string 22 } 23 24 var ( 25 matchComplexityOnce sync.Once 26 validChars string 27 requiredList []complexity 28 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 }, 46 } 47 ) 48 49 // NewComplexity for preparation 50 func NewComplexity() { 51 matchComplexityOnce.Do(func() { 52 setupComplexity(setting.PasswordComplexity) 53 }) 54 } 55 56 func setupComplexity(values []string) { 57 if len(values) != 1 || values[0] != "off" { 58 for _, val := range values { 59 if complex, ok := charComplexities[val]; ok { 60 validChars += complex.ValidChars 61 requiredList = append(requiredList, complex) 62 } 63 } 64 if len(requiredList) == 0 { 65 // No valid character classes found; use all classes as default 66 for _, complex := range charComplexities { 67 validChars += complex.ValidChars 68 requiredList = append(requiredList, complex) 69 } 70 } 71 } 72 if validChars == "" { 73 // No complexities to check; provide a sensible default for password generation 74 validChars = charComplexities["lower"].ValidChars + charComplexities["upper"].ValidChars + charComplexities["digit"].ValidChars 75 } 76 } 77 78 // IsComplexEnough return True if password meets complexity settings 79 func IsComplexEnough(pwd string) bool { 80 NewComplexity() 81 if len(validChars) > 0 { 82 for _, req := range requiredList { 83 if !strings.ContainsAny(req.ValidChars, pwd) { 84 return false 85 } 86 } 87 } 88 return true 89 } 90 91 // Generate a random password 92 func Generate(n int) (string, error) { 93 NewComplexity() 94 buffer := make([]byte, n) 95 max := big.NewInt(int64(len(validChars))) 96 for { 97 for j := 0; j < n; j++ { 98 rnd, err := rand.Int(rand.Reader, max) 99 if err != nil { 100 return "", err 101 } 102 buffer[j] = validChars[rnd.Int64()] 103 } 104 pwned, err := IsPwned(goContext.Background(), string(buffer)) 105 if err != nil { 106 return "", err 107 } 108 if IsComplexEnough(string(buffer)) && !pwned && string(buffer[0]) != " " && string(buffer[n-1]) != " " { 109 return string(buffer), nil 110 } 111 } 112 } 113 114 // BuildComplexityError builds the error message when password complexity checks fail 115 func BuildComplexityError(locale translation.Locale) string { 116 var buffer bytes.Buffer 117 buffer.WriteString(locale.Tr("form.password_complexity")) 118 buffer.WriteString("<ul>") 119 for _, c := range requiredList { 120 buffer.WriteString("<li>") 121 buffer.WriteString(locale.Tr(c.TrNameOne)) 122 buffer.WriteString("</li>") 123 } 124 buffer.WriteString("</ul>") 125 return buffer.String() 126 }