github.com/MetalBlockchain/metalgo@v1.11.9/utils/password/password.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package password 5 6 import ( 7 "errors" 8 "fmt" 9 10 "github.com/nbutton23/zxcvbn-go" 11 ) 12 13 // Strength is the strength of a password 14 type Strength int 15 16 const ( 17 // The scoring mechanism of the zxcvbn package is defined as follows: 18 // 0 # too guessable: risky password. (guesses < 10^3) 19 // 1 # very guessable: protection from throttled online attacks. (guesses < 10^6) 20 // 2 # somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8) 21 // 3 # safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10) 22 // 4 # very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10) 23 // Note: We could use the iota keyword to define each of the below consts, but I think in this case 24 // it's better to be explicit 25 26 // VeryWeak password 27 VeryWeak = 0 28 // Weak password 29 Weak = 1 30 // Fair password 31 Fair = 2 32 // Strong password 33 Strong = 3 34 // VeryStrong password 35 VeryStrong = 4 36 37 // OK password is the recommended minimum strength for API calls 38 OK = Fair 39 40 // maxCheckedPassLen limits the length of the password that should be 41 // strength checked. 42 // 43 // As per issue https://github.com/MetalBlockchain/metalgo/issues/195 it was found 44 // the longer the length of password the slower zxcvbn.PasswordStrength() 45 // performs. To avoid performance issues, and a DoS vector, we only strength 46 // check the first 50 characters of the password. 47 maxCheckedPassLen = 50 48 49 // maxPassLen is the maximum allowed password length 50 maxPassLen = 1024 51 ) 52 53 var ( 54 ErrEmptyPassword = errors.New("empty password") 55 ErrPassMaxLength = fmt.Errorf("password exceeds maximum length of %d chars", maxPassLen) 56 ErrWeakPassword = errors.New("password is too weak") 57 ) 58 59 // SufficientlyStrong returns true if [password] has strength greater than or 60 // equal to [minimumStrength] 61 func SufficientlyStrong(password string, minimumStrength Strength) bool { 62 if len(password) > maxCheckedPassLen { 63 password = password[:maxCheckedPassLen] 64 } 65 return zxcvbn.PasswordStrength(password, nil).Score >= int(minimumStrength) 66 } 67 68 // IsValid returns nil if [password] is a reasonable length and has strength 69 // greater than or equal to [minimumStrength] 70 func IsValid(password string, minimumStrength Strength) error { 71 switch { 72 case len(password) == 0: 73 return ErrEmptyPassword 74 case len(password) > maxPassLen: 75 return ErrPassMaxLength 76 case !SufficientlyStrong(password, minimumStrength): 77 return ErrWeakPassword 78 default: 79 return nil 80 } 81 }