code.gitea.io/gitea@v1.22.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 "context" 9 "crypto/rand" 10 "errors" 11 "html/template" 12 "math/big" 13 "strings" 14 "sync" 15 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/translation" 18 ) 19 20 var ( 21 ErrComplexity = errors.New("password not complex enough") 22 ErrMinLength = errors.New("password not long enough") 23 ) 24 25 // complexity contains information about a particular kind of password complexity 26 type complexity struct { 27 ValidChars string 28 TrNameOne string 29 } 30 31 var ( 32 matchComplexityOnce sync.Once 33 validChars string 34 requiredList []complexity 35 36 charComplexities = map[string]complexity{ 37 "lower": { 38 `abcdefghijklmnopqrstuvwxyz`, 39 "form.password_lowercase_one", 40 }, 41 "upper": { 42 `ABCDEFGHIJKLMNOPQRSTUVWXYZ`, 43 "form.password_uppercase_one", 44 }, 45 "digit": { 46 `0123456789`, 47 "form.password_digit_one", 48 }, 49 "spec": { 50 ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`", 51 "form.password_special_one", 52 }, 53 } 54 ) 55 56 // NewComplexity for preparation 57 func NewComplexity() { 58 matchComplexityOnce.Do(func() { 59 setupComplexity(setting.PasswordComplexity) 60 }) 61 } 62 63 func setupComplexity(values []string) { 64 if len(values) != 1 || values[0] != "off" { 65 for _, val := range values { 66 if complexity, ok := charComplexities[val]; ok { 67 validChars += complexity.ValidChars 68 requiredList = append(requiredList, complexity) 69 } 70 } 71 if len(requiredList) == 0 { 72 // No valid character classes found; use all classes as default 73 for _, complexity := range charComplexities { 74 validChars += complexity.ValidChars 75 requiredList = append(requiredList, complexity) 76 } 77 } 78 } 79 if validChars == "" { 80 // No complexities to check; provide a sensible default for password generation 81 validChars = charComplexities["lower"].ValidChars + charComplexities["upper"].ValidChars + charComplexities["digit"].ValidChars 82 } 83 } 84 85 // IsComplexEnough return True if password meets complexity settings 86 func IsComplexEnough(pwd string) bool { 87 NewComplexity() 88 if len(validChars) > 0 { 89 for _, req := range requiredList { 90 if !strings.ContainsAny(req.ValidChars, pwd) { 91 return false 92 } 93 } 94 } 95 return true 96 } 97 98 // Generate a random password 99 func Generate(n int) (string, error) { 100 NewComplexity() 101 buffer := make([]byte, n) 102 max := big.NewInt(int64(len(validChars))) 103 for { 104 for j := 0; j < n; j++ { 105 rnd, err := rand.Int(rand.Reader, max) 106 if err != nil { 107 return "", err 108 } 109 buffer[j] = validChars[rnd.Int64()] 110 } 111 112 if err := IsPwned(context.Background(), string(buffer)); err != nil { 113 if errors.Is(err, ErrIsPwned) { 114 continue 115 } 116 return "", err 117 } 118 if IsComplexEnough(string(buffer)) && string(buffer[0]) != " " && string(buffer[n-1]) != " " { 119 return string(buffer), nil 120 } 121 } 122 } 123 124 // BuildComplexityError builds the error message when password complexity checks fail 125 func BuildComplexityError(locale translation.Locale) template.HTML { 126 var buffer bytes.Buffer 127 buffer.WriteString(locale.TrString("form.password_complexity")) 128 buffer.WriteString("<ul>") 129 for _, c := range requiredList { 130 buffer.WriteString("<li>") 131 buffer.WriteString(locale.TrString(c.TrNameOne)) 132 buffer.WriteString("</li>") 133 } 134 buffer.WriteString("</ul>") 135 return template.HTML(buffer.String()) 136 }