88 lines
2.2 KiB
Go
88 lines
2.2 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
|
|
|
|
// Where to put messages by default. FK: mailbox.id
|
|
DefaultMailbox uint64
|
|
}
|
|
|
|
// 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) {
|
|
accounts := make([]Account, len(usernames))
|
|
|
|
// FIXME: I don't know how to storm, it turns out
|
|
for i, username := range usernames {
|
|
account, err := c.FindAccount(username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
accounts[i] = account
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
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
|
|
}
|