diff --git a/internal/imap/session.go b/internal/imap/session.go index adf27d3..271aa32 100644 --- a/internal/imap/session.go +++ b/internal/imap/session.go @@ -16,7 +16,7 @@ var ( // type Session implements the User interface for emersion/go-imap type Session struct { ID uint64 - Account *store.Account + Account store.Account ServiceName string } diff --git a/internal/smtp/sender.go b/internal/smtp/sender.go index 7646eb3..0c4fb76 100644 --- a/internal/smtp/sender.go +++ b/internal/smtp/sender.go @@ -11,7 +11,7 @@ import ( // logged-in account per-session type sender struct { msa *msa - account *store.Account + account store.Account } func (s *sender) ServeSMTP(from string, to []string, r io.Reader) error { diff --git a/internal/store/account.go b/internal/store/account.go index 079954e..9a4e3f2 100644 --- a/internal/store/account.go +++ b/internal/store/account.go @@ -1,18 +1,16 @@ package store import ( + "fmt" + "golang.org/x/crypto/bcrypt" ) -// 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 +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 @@ -32,3 +30,46 @@ type Account struct { // 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 +} diff --git a/internal/store/store.go b/internal/store/store.go index 0e63f13..7e496ed 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -18,9 +18,7 @@ type Interface interface { SetDomain(string) error SetTLS([]byte, []byte) error - CreateAccount(*Account) error - FindAccount(string) (*Account, error) - FindAccountWithPassword(string, string) (*Account, error) + AccountInterface io.Closer } @@ -142,32 +140,6 @@ func (c *concrete) SetTLS(certPEM, keyPEM []byte) error { return 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) 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 nil, err - } - - if !CheckPassword(account.PasswordHash, password) { - return nil, fmt.Errorf("bad password") - } - - return account, nil -} - func (c *concrete) Close() error { return c.storm.Close() }