2019-10-14 22:24:26 +07:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-10-14 22:24:26 +07:00
package password
import (
2019-11-19 19:44:58 -03:00
"bytes"
2024-02-04 14:29:09 +01:00
"context"
2019-10-14 22:24:26 +07:00
"crypto/rand"
2024-02-04 14:29:09 +01:00
"errors"
2024-02-15 05:48:45 +08:00
"html/template"
2019-10-14 22:24:26 +07:00
"math/big"
2019-10-16 00:09:58 -03:00
"strings"
2019-10-14 22:24:26 +07:00
"sync"
"code.gitea.io/gitea/modules/setting"
2023-02-19 07:35:20 +00:00
"code.gitea.io/gitea/modules/translation"
2019-10-14 22:24:26 +07:00
)
2024-02-04 14:29:09 +01:00
var (
ErrComplexity = errors . New ( "password not complex enough" )
ErrMinLength = errors . New ( "password not long enough" )
)
2019-11-19 19:44:58 -03:00
// complexity contains information about a particular kind of password complexity
type complexity struct {
ValidChars string
TrNameOne string
}
2019-10-16 00:09:58 -03:00
var (
matchComplexityOnce sync . Once
validChars string
2019-11-19 19:44:58 -03:00
requiredList [ ] complexity
2019-10-16 00:09:58 -03:00
2019-11-19 19:44:58 -03:00
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" ,
} ,
2019-10-16 00:09:58 -03:00
}
)
2019-10-14 22:24:26 +07:00
// NewComplexity for preparation
func NewComplexity ( ) {
matchComplexityOnce . Do ( func ( ) {
2019-10-16 00:09:58 -03:00
setupComplexity ( setting . PasswordComplexity )
} )
}
func setupComplexity ( values [ ] string ) {
if len ( values ) != 1 || values [ 0 ] != "off" {
for _ , val := range values {
2024-04-22 13:48:42 +02:00
if complexity , ok := charComplexities [ val ] ; ok {
validChars += complexity . ValidChars
requiredList = append ( requiredList , complexity )
2019-10-14 22:24:26 +07:00
}
2019-10-16 00:09:58 -03:00
}
2019-11-19 19:44:58 -03:00
if len ( requiredList ) == 0 {
2019-10-16 00:09:58 -03:00
// No valid character classes found; use all classes as default
2024-04-22 13:48:42 +02:00
for _ , complexity := range charComplexities {
validChars += complexity . ValidChars
requiredList = append ( requiredList , complexity )
2019-10-14 22:24:26 +07:00
}
}
2019-10-16 00:09:58 -03:00
}
if validChars == "" {
// No complexities to check; provide a sensible default for password generation
2019-11-19 19:44:58 -03:00
validChars = charComplexities [ "lower" ] . ValidChars + charComplexities [ "upper" ] . ValidChars + charComplexities [ "digit" ] . ValidChars
2019-10-16 00:09:58 -03:00
}
2019-10-14 22:24:26 +07:00
}
2019-10-16 00:09:58 -03:00
// IsComplexEnough return True if password meets complexity settings
2019-10-14 22:24:26 +07:00
func IsComplexEnough ( pwd string ) bool {
2019-10-16 00:09:58 -03:00
NewComplexity ( )
if len ( validChars ) > 0 {
2019-11-19 19:44:58 -03:00
for _ , req := range requiredList {
if ! strings . ContainsAny ( req . ValidChars , pwd ) {
2019-10-14 22:24:26 +07:00
return false
}
}
}
return true
}
2020-09-08 17:06:39 -05:00
// Generate a random password
2019-10-14 22:24:26 +07:00
func Generate ( n int ) ( string , error ) {
NewComplexity ( )
buffer := make ( [ ] byte , n )
2024-12-15 03:31:07 +01:00
maxInt := big . NewInt ( int64 ( len ( validChars ) ) )
2019-10-14 22:24:26 +07:00
for {
for j := 0 ; j < n ; j ++ {
2024-12-15 03:31:07 +01:00
rnd , err := rand . Int ( rand . Reader , maxInt )
2019-10-14 22:24:26 +07:00
if err != nil {
return "" , err
}
buffer [ j ] = validChars [ rnd . Int64 ( ) ]
}
2024-02-04 14:29:09 +01:00
if err := IsPwned ( context . Background ( ) , string ( buffer ) ) ; err != nil {
if errors . Is ( err , ErrIsPwned ) {
continue
}
2020-09-08 17:06:39 -05:00
return "" , err
}
2024-02-04 14:29:09 +01:00
if IsComplexEnough ( string ( buffer ) ) && string ( buffer [ 0 ] ) != " " && string ( buffer [ n - 1 ] ) != " " {
2019-10-14 22:24:26 +07:00
return string ( buffer ) , nil
}
}
}
2019-11-19 19:44:58 -03:00
// BuildComplexityError builds the error message when password complexity checks fail
2024-02-15 05:48:45 +08:00
func BuildComplexityError ( locale translation . Locale ) template . HTML {
2019-11-19 19:44:58 -03:00
var buffer bytes . Buffer
2024-02-15 05:48:45 +08:00
buffer . WriteString ( locale . TrString ( "form.password_complexity" ) )
2019-11-19 19:44:58 -03:00
buffer . WriteString ( "<ul>" )
for _ , c := range requiredList {
buffer . WriteString ( "<li>" )
2024-02-15 05:48:45 +08:00
buffer . WriteString ( locale . TrString ( c . TrNameOne ) )
2019-11-19 19:44:58 -03:00
buffer . WriteString ( "</li>" )
}
buffer . WriteString ( "</ul>" )
2024-02-15 05:48:45 +08:00
return template . HTML ( buffer . String ( ) )
2019-11-19 19:44:58 -03:00
}