github.com/tommi2day/gomodules@v1.13.2-0.20240423190010-b7d55d252a27/pwlib/password_check.go (about)

     1  package pwlib
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	log "github.com/sirupsen/logrus"
     8  )
     9  
    10  // SilentCheck skip log messages while checking
    11  var SilentCheck = false
    12  
    13  // SetSpecialChars change default charset for checks
    14  func SetSpecialChars(specialChars string) {
    15  	all := UpperChar + LowerChar + Digits + specialChars
    16  	charset = PasswordCharset{UpperChar, LowerChar, Digits, specialChars, all}
    17  }
    18  
    19  // DoPasswordCheck Checks a password to given criteria
    20  func DoPasswordCheck(password string, length int, upper int, lower int, numeric int, special int, firstCharCheck bool, allowedChars string) bool {
    21  	// var ls = true
    22  	var ucs = true
    23  	var lcs = true
    24  	var ncs = true
    25  	var sps = true
    26  	// var cs = true
    27  	var fcs = true
    28  	var err error
    29  
    30  	// allowed chars
    31  	possible := allowedChars
    32  	if allowedChars == "" {
    33  		possible = charset.AllChars
    34  	}
    35  
    36  	// do checks
    37  	ls, err := checkLength(password, length)
    38  	logError("length", err)
    39  	if upper > 0 {
    40  		ucs, err = checkClass(password, upper, charset.UpperChar)
    41  		logError("uppercase", err)
    42  	}
    43  	if lower > 0 {
    44  		lcs, err = checkClass(password, lower, charset.LowerChar)
    45  		logError("lowercase", err)
    46  	}
    47  	if numeric > 0 {
    48  		ncs, err = checkClass(password, numeric, charset.Digits)
    49  		logError("numeric", err)
    50  	}
    51  	if special > 0 {
    52  		sps, err = checkClass(password, special, charset.SpecialChar)
    53  		logError("special", err)
    54  	}
    55  	cs, err := checkChars(password, possible)
    56  	logError("allowed chars", err)
    57  	if firstCharCheck {
    58  		fcs, err = checkFirstChar(password, charset.UpperChar+charset.LowerChar)
    59  		logError("first character", err)
    60  	}
    61  
    62  	// final state
    63  	return ls && ucs && lcs && ncs && sps && cs && fcs
    64  }
    65  
    66  func logError(name string, err error) {
    67  	if SilentCheck {
    68  		return
    69  	}
    70  	if err != nil {
    71  		log.Errorf("%s check failed: %s", name, err.Error())
    72  	} else {
    73  		log.Debugf("%s check passed", name)
    74  	}
    75  }
    76  
    77  func checkClass(
    78  	password string,
    79  	should int,
    80  	chars string,
    81  ) (bool, error) {
    82  	if len(password) == 0 {
    83  		return false, fmt.Errorf("password empty")
    84  	}
    85  	cnt := 0
    86  	for _, char := range strings.Split(chars, "") {
    87  		cnt += strings.Count(password, char)
    88  	}
    89  	if cnt < should {
    90  		return false, fmt.Errorf("at least %d chars out of '%s' expected", should, chars)
    91  	}
    92  	return true, nil
    93  }
    94  
    95  func checkChars(
    96  	password string,
    97  	chars string,
    98  ) (bool, error) {
    99  	if len(password) == 0 {
   100  		return false, fmt.Errorf("password empty")
   101  	}
   102  	data := []rune(password)
   103  	for i := 0; i < len(data); i++ {
   104  		r := data[i]
   105  		idx := strings.IndexRune(chars, r)
   106  		if idx == -1 {
   107  			return false, fmt.Errorf("only %s allowed", chars)
   108  		}
   109  	}
   110  	return true, nil
   111  }
   112  
   113  func checkLength(password string, minlen int) (bool, error) {
   114  	length := len(password)
   115  	if length < minlen {
   116  		return false, fmt.Errorf("at least  %d chars expected, have %d", minlen, length)
   117  	}
   118  	return true, nil
   119  }
   120  
   121  func checkFirstChar(
   122  	password string,
   123  	allowed string,
   124  ) (bool, error) {
   125  	if len(password) == 0 {
   126  		return false, fmt.Errorf("%s check failed, password empty", "first letter")
   127  	}
   128  	firstLetter := []rune(password)[0]
   129  	idx := strings.IndexRune(allowed, firstLetter)
   130  	if idx == -1 {
   131  		return false, fmt.Errorf("%s check failed, only %s allowed", "first letter", allowed)
   132  	}
   133  	return true, nil
   134  }