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  }