mirror of
https://github.com/gophish/gophish
synced 2024-11-14 16:27:23 +00:00
bb7de8df3e
This PR adds the initial work to implement a password policy as defined in #1538. Specifically, this implements the following * Rate limiting for the login handler * Implementing the ability for system admins to require a user to reset their password * Implementing a password policy that requires passwords to be a minimum of 8 characters * Removes the default password (gophish) for admin users to instead have the password randomly generated when Gophish first starts up * Adds a password strength meter when choosing a new password Fixes #1538
103 lines
3.3 KiB
Go
103 lines
3.3 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// MinPasswordLength is the minimum number of characters required in a password
|
|
const MinPasswordLength = 8
|
|
|
|
// APIKeyLength is the length of Gophish API keys
|
|
const APIKeyLength = 32
|
|
|
|
// ErrInvalidPassword is thrown when a user provides an incorrect password.
|
|
var ErrInvalidPassword = errors.New("Invalid Password")
|
|
|
|
// ErrPasswordMismatch is thrown when a user provides a mismatching password
|
|
// and confirmation password.
|
|
var ErrPasswordMismatch = errors.New("Passwords do not match")
|
|
|
|
// ErrReusedPassword is thrown when a user attempts to change their password to
|
|
// the existing password
|
|
var ErrReusedPassword = errors.New("Cannot reuse existing password")
|
|
|
|
// ErrEmptyPassword is thrown when a user provides a blank password to the register
|
|
// or change password functions
|
|
var ErrEmptyPassword = errors.New("No password provided")
|
|
|
|
// ErrPasswordTooShort is thrown when a user provides a password that is less
|
|
// than MinPasswordLength
|
|
var ErrPasswordTooShort = fmt.Errorf("Password must be at least %d characters", MinPasswordLength)
|
|
|
|
// GenerateSecureKey returns the hex representation of key generated from n
|
|
// random bytes
|
|
func GenerateSecureKey(n int) string {
|
|
k := make([]byte, n)
|
|
io.ReadFull(rand.Reader, k)
|
|
return fmt.Sprintf("%x", k)
|
|
}
|
|
|
|
// GeneratePasswordHash returns the bcrypt hash for the provided password using
|
|
// the default bcrypt cost.
|
|
func GeneratePasswordHash(password string) (string, error) {
|
|
h, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(h), nil
|
|
}
|
|
|
|
// CheckPasswordPolicy ensures the provided password is valid according to our
|
|
// password policy.
|
|
//
|
|
// The current password policy is simply a minimum of 8 characters, though this
|
|
// may change in the future (see #1538).
|
|
func CheckPasswordPolicy(password string) error {
|
|
switch {
|
|
// Admittedly, empty passwords are a subset of too short passwords, but it
|
|
// helps to provide a more specific error message
|
|
case password == "":
|
|
return ErrEmptyPassword
|
|
case len(password) < MinPasswordLength:
|
|
return ErrPasswordTooShort
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidatePassword validates that the provided password matches the provided
|
|
// bcrypt hash.
|
|
func ValidatePassword(password string, hash string) error {
|
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
|
}
|
|
|
|
// ValidatePasswordChange validates that the new password matches the
|
|
// configured password policy, that the new password and confirmation
|
|
// password match.
|
|
//
|
|
// Note that this assumes the current password has been confirmed by the
|
|
// caller.
|
|
//
|
|
// If all of the provided data is valid, then the hash of the new password is
|
|
// returned.
|
|
func ValidatePasswordChange(currentHash, newPassword, confirmPassword string) (string, error) {
|
|
// Ensure the new password passes our password policy
|
|
if err := CheckPasswordPolicy(newPassword); err != nil {
|
|
return "", err
|
|
}
|
|
// Check that new passwords match
|
|
if newPassword != confirmPassword {
|
|
return "", ErrPasswordMismatch
|
|
}
|
|
// Make sure that the new password isn't the same as the old one
|
|
err := ValidatePassword(newPassword, currentHash)
|
|
if err == nil {
|
|
return "", ErrReusedPassword
|
|
}
|
|
// Generate the new hash
|
|
return GeneratePasswordHash(newPassword)
|
|
}
|