github.com/wostzone/hub/auth@v0.0.0-20220118060317-7bb375743b17/pkg/authenticate/Authenticator.go (about)

     1  package authenticate
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/alexedwards/argon2id"
     7  	"github.com/sirupsen/logrus"
     8  	"golang.org/x/crypto/bcrypt"
     9  )
    10  
    11  // supported password hashes
    12  const (
    13  	PWHASH_ARGON2id = "argon2id"
    14  	PWHASH_BCRYPT   = "bcrypt" // fallback in case argon2i cannot be used
    15  )
    16  
    17  // VerifyUsernamePassword is an interface to verify username/password authentication
    18  type VerifyUsernamePassword func(userID string, password string) bool
    19  
    20  // Authenticator manages client username/password authentication for access to Things
    21  type Authenticator struct {
    22  	unpwStore IUnpwStore
    23  }
    24  
    25  // CreatePasswordHash for the given password
    26  // This creates the hash and does not update the store. See also VerifyPasswordHash
    27  // The only two hashes allowed are argon2id and bcrypt, although argon2id is recommended
    28  //  password to hash
    29  //  algo is the algorithm to use, PWHASH_ARGON2id (default) or PWHASH_BCRYPT
    30  //  iterations for argon2id, default is 10
    31  func CreatePasswordHash(password string, algo string, iterations uint) (hash string, err error) {
    32  	if password == "" {
    33  		return "", fmt.Errorf("CreatePasswordHash: Missing password")
    34  	}
    35  	if algo == "" {
    36  		algo = PWHASH_ARGON2id
    37  	}
    38  	if algo == PWHASH_ARGON2id {
    39  		if iterations <= 0 {
    40  			iterations = 10
    41  		}
    42  		params := argon2id.DefaultParams
    43  		params.Iterations = uint32(iterations)
    44  		hash, err = argon2id.CreateHash(password, params)
    45  	} else if algo == PWHASH_BCRYPT {
    46  		var hashBytes []byte
    47  		hashBytes, err = bcrypt.GenerateFromPassword([]byte(password), 0)
    48  		hash = string(hashBytes)
    49  	} else {
    50  		err = fmt.Errorf("CreatePasswordHash: Unsupported hashing algorithm '%s'", algo)
    51  	}
    52  	return hash, err
    53  }
    54  
    55  // SetPassword hashes the given password and stores it in the password store
    56  // Returns if username or password are not provided
    57  func (ah *Authenticator) SetPassword(username string, password string) error {
    58  	if username == "" || password == "" {
    59  		return fmt.Errorf("SetPassword: Missing username or password")
    60  	}
    61  	// use default hashing algo
    62  	hash, err := CreatePasswordHash(password, "", 0)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	if ah.unpwStore != nil {
    67  		err = ah.unpwStore.SetPasswordHash(username, hash)
    68  	}
    69  	return err
    70  }
    71  
    72  // Start the authhandler. This opens the password store.
    73  // if no password store was provided this simply returns nil
    74  func (ah *Authenticator) Start() error {
    75  	if ah.unpwStore == nil {
    76  		return fmt.Errorf("Authenticator.Start: missing password store")
    77  	}
    78  	err := ah.unpwStore.Open()
    79  	if err != nil {
    80  		err2 := fmt.Errorf("Authenticator.Start Failed opening password store: %s", err)
    81  		logrus.Errorf("%s", err2)
    82  		return err2
    83  	}
    84  	logrus.Infof("Authenticator.Start Success")
    85  	return nil
    86  }
    87  
    88  // Stop the auth handler and close the password store.
    89  func (ah *Authenticator) Stop() {
    90  	if ah.unpwStore != nil {
    91  		ah.unpwStore.Close()
    92  	}
    93  }
    94  
    95  // VerifyUsernamePassword verifies if the given password is valid for login
    96  // Returns true if valid, false if the user is unknown or the password is invalid
    97  func (ah *Authenticator) VerifyUsernamePassword(loginName string, password string) bool {
    98  	if ah.unpwStore == nil {
    99  		return false
   100  	}
   101  
   102  	// Todo: configure hashing method
   103  	algo := PWHASH_ARGON2id
   104  	h := ah.unpwStore.GetPasswordHash(loginName)
   105  	match := ah.VerifyPasswordHash(h, password, algo)
   106  	logrus.Infof("VerifyUsernamePassword: loginName=%s, match=%v", loginName, match)
   107  	return match
   108  }
   109  
   110  // VerifyPasswordHash verifies if the given hash matches the password
   111  // This does not access the store
   112  //  hash to verify
   113  //  password to verify against
   114  //  algo is the algorithm to use, PWHASH_ARGON2id or PWHASH_BCRYPT
   115  // returns true if the password matches the hash, or false on mismatch
   116  func (ah *Authenticator) VerifyPasswordHash(hash string, password string, algo string) bool {
   117  	if algo == PWHASH_ARGON2id {
   118  		match, _ := argon2id.ComparePasswordAndHash(password, hash)
   119  		return match
   120  	} else if algo == PWHASH_BCRYPT {
   121  		err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
   122  		return (err == nil)
   123  	}
   124  	return false
   125  }
   126  
   127  // NewAuthenticator creates a new instance of the authentication handler to update and verify user passwords.
   128  //  unpwStore provides the functions to access the password store.
   129  func NewAuthenticator(unpwStore IUnpwStore) *Authenticator {
   130  	a := Authenticator{
   131  		unpwStore: unpwStore,
   132  	}
   133  	return &a
   134  }