72 lines
1.8 KiB
Go
72 lines
1.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"gardomatic.kleiax.de/internal/platform/validate"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// Password holds a bcrypt hash and, while constructing a new password, its
|
|
// plaintext value for policy validation. Plaintext is never exposed.
|
|
type Password struct {
|
|
plaintext *string
|
|
hash []byte
|
|
}
|
|
|
|
// NewPassword reconstructs a password value from an existing bcrypt hash.
|
|
func NewPassword(hash []byte) *Password {
|
|
return &Password{hash: hash}
|
|
}
|
|
|
|
// Set hashes plaintextPassword and replaces the stored hash.
|
|
func (p *Password) Set(plaintextPassword string) error {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.plaintext = &plaintextPassword
|
|
p.hash = hash
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get returns a defensive copy of the bcrypt hash.
|
|
func (p *Password) Get() []byte {
|
|
return p.hash
|
|
}
|
|
|
|
// Matches reports whether plaintextPassword matches the stored bcrypt hash.
|
|
func (p *Password) Matches(plaintextPassword string) (bool, error) {
|
|
err := bcrypt.CompareHashAndPassword(p.hash, []byte(plaintextPassword))
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
|
|
return false, nil
|
|
default:
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Validate adds password-hash validation errors to v.
|
|
func (p *Password) Validate(v *validate.Validator) {
|
|
if p.plaintext != nil {
|
|
ValidatePasswordPlaintext(v, *p.plaintext)
|
|
}
|
|
|
|
if p.hash == nil {
|
|
panic("missing password hash for user")
|
|
}
|
|
}
|
|
|
|
// ValidatePasswordPlaintext applies the password policy to plaintext input.
|
|
func ValidatePasswordPlaintext(v *validate.Validator, plaintext string) {
|
|
v.Check(plaintext != "", "password", "must be provided")
|
|
v.Check(len(plaintext) >= 8, "password", "must be at least 8 bytes long")
|
|
v.Check(len(plaintext) <= 72, "password", "must not be more than 72 bytes long")
|
|
}
|