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//accounts//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) { 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 }