Files
crockery/internal/store/account.go

76 lines
2.0 KiB
Go

package store
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
type AccountInterface interface {
CreateAccount(*Account) error
FindAccount(string) (Account, error)
FindAccounts(...string) ([]Account, error)
FindAccountWithPassword(string, string) (Account, error)
}
// Account is stored in the database as domains/<domain>/accounts/<id>/config
type Account struct {
// The username is typically user@example.com - the user is required to
// enter it *exactly* to log in.
Username string `storm:"id"`
// Whether the user is an admin or not. Typically, only postmaster@example.com
// is an admin, but others may be set up too if desired.
Admin bool
// Wildcard users are last-resort destinations for incoming mail. A domain
// should only have one of them.
Wildcard bool `storm:"index"`
// As generated by HashPassword
PasswordHash string
}
// HashPassword turns a plaintext password into a crypt()ed string, using bcrypt
func HashPassword(password string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(b), err
}
func CheckPassword(hashed, plain string) bool {
return bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)) == nil
}
func (c *concrete) CreateAccount(account *Account) error {
return c.storm.Save(account)
}
func (c *concrete) FindAccount(username string) (Account, error) {
var account Account
return account, c.storm.One("Username", username, &account)
}
func (c *concrete) FindAccounts(usernames ...string) ([]Account, error) {
var accounts []Account
return accounts, c.storm.Find("Username", usernames, &accounts)
}
func (c *concrete) FindAccountWithPassword(username, password string) (Account, error) {
account, err := c.FindAccount(username)
if err != nil {
// Always do a bcrypt check to avoid timing attacks
_ = CheckPassword("", "")
return Account{}, err
}
if !CheckPassword(account.PasswordHash, password) {
return Account{}, fmt.Errorf("bad password")
}
return account, nil
}