diff --git a/api/user.go b/api/user.go index 3cf0cab..13dae5d 100644 --- a/api/user.go +++ b/api/user.go @@ -12,7 +12,7 @@ import ( // The UserDatabase interface for encapsulating database access. type UserDatabase interface { - CreateUser(user *model.User) error + CreateUser(user model.ExternalUserWithCredentials) (*model.User, error) DeleteUser(user *model.User) error GetUserByID(ID uint) (*model.User, error) GetUserByName(name string) (*model.User, error) @@ -44,14 +44,14 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) { return } - user := externalUser.IntoInternalUser() - - if h.userExists(user.Name) { + if h.userExists(externalUser.Name) { ctx.AbortWithError(http.StatusBadRequest, errors.New("username already exists")) return } - if success := successOrAbort(ctx, http.StatusInternalServerError, h.DB.CreateUser(user)); !success { + user, err := h.DB.CreateUser(externalUser) + + if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { return } diff --git a/app.go b/app.go index 465d060..509b7e1 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "os/signal" "syscall" + "github.com/eikendev/pushbits/authentication/credentials" "github.com/eikendev/pushbits/configuration" "github.com/eikendev/pushbits/database" "github.com/eikendev/pushbits/dispatcher" @@ -30,7 +31,9 @@ func main() { c := configuration.Get() - db, err := database.Create(c.Database.Dialect, c.Database.Connection) + cm := credentials.CreateManager(c.Crypto) + + db, err := database.Create(cm, c.Database.Dialect, c.Database.Connection) if err != nil { panic(err) } diff --git a/authentication/credentials/credentials.go b/authentication/credentials/credentials.go index dcdcfba..5ba5e15 100644 --- a/authentication/credentials/credentials.go +++ b/authentication/credentials/credentials.go @@ -3,28 +3,27 @@ package credentials import ( "log" + "github.com/eikendev/pushbits/configuration" + "github.com/alexedwards/argon2id" ) -// CreatePasswordHash returns a hashed version of the given password. -func CreatePasswordHash(password string) []byte { - hash, err := argon2id.CreateHash(password, argon2id.DefaultParams) - - if err != nil { - panic(err) - } - - return []byte(hash) +// Manager holds information for managing credentials. +type Manager struct { + argon2Params *argon2id.Params } -// ComparePassword compares a hashed password with its possible plaintext equivalent. -func ComparePassword(hash, password []byte) bool { - match, err := argon2id.ComparePasswordAndHash(string(password), string(hash)) +// CreateManager instanciates a credential manager. +func CreateManager(c configuration.CryptoConfig) *Manager { + log.Println("Setting up credential manager.") - if err != nil { - log.Fatal(err) - return false + argon2Params := &argon2id.Params{ + Memory: c.Argon2.Memory, + Iterations: c.Argon2.Iterations, + Parallelism: c.Argon2.Parallelism, + SaltLength: c.Argon2.SaltLength, + KeyLength: c.Argon2.KeyLength, } - return match + return &Manager{argon2Params: argon2Params} } diff --git a/authentication/credentials/password.go b/authentication/credentials/password.go new file mode 100644 index 0000000..b2896b4 --- /dev/null +++ b/authentication/credentials/password.go @@ -0,0 +1,31 @@ +package credentials + +import ( + "log" + + "github.com/alexedwards/argon2id" +) + +// CreatePasswordHash returns a hashed version of the given password. +func (m *Manager) CreatePasswordHash(password string) []byte { + hash, err := argon2id.CreateHash(password, m.argon2Params) + + if err != nil { + log.Fatal(err) + panic(err) + } + + return []byte(hash) +} + +// ComparePassword compares a hashed password with its possible plaintext equivalent. +func ComparePassword(hash, password []byte) bool { + match, err := argon2id.ComparePasswordAndHash(string(password), string(hash)) + + if err != nil { + log.Fatal(err) + return false + } + + return match +} diff --git a/configuration/configuration.go b/configuration/configuration.go index 0644462..ec14d07 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -4,6 +4,20 @@ import ( "github.com/jinzhu/configor" ) +// Argon2Config holds the parameters used for creating hashes with Argon2. +type Argon2Config struct { + Memory uint32 `default:"65536"` + Iterations uint32 `default:"1"` + Parallelism uint8 `default:"2"` + SaltLength uint32 `default:"16"` + KeyLength uint32 `default:"32"` +} + +// CryptoConfig holds the parameters used for creating hashes. +type CryptoConfig struct { + Argon2 Argon2Config +} + // Configuration holds values that can be configured by the user. type Configuration struct { Database struct { @@ -20,6 +34,7 @@ type Configuration struct { Username string `required:"true"` Password string `required:"true"` } + Crypto CryptoConfig } func configFiles() []string { diff --git a/database/database.go b/database/database.go index beed921..83220f8 100644 --- a/database/database.go +++ b/database/database.go @@ -18,8 +18,9 @@ import ( // Database holds information for the database connection. type Database struct { - gormdb *gorm.DB - sqldb *sql.DB + gormdb *gorm.DB + sqldb *sql.DB + credentialsManager *credentials.Manager } func createFileDir(file string) { @@ -31,7 +32,7 @@ func createFileDir(file string) { } // Create instanciates a database connection. -func Create(dialect, connection string) (*Database, error) { +func Create(cm *credentials.Manager, dialect, connection string) (*Database, error) { log.Println("Setting up database connection.") maxOpenConns := 5 @@ -68,7 +69,7 @@ func Create(dialect, connection string) (*Database, error) { db.AutoMigrate(&model.User{}, &model.Application{}) - return &Database{gormdb: db, sqldb: sql}, nil + return &Database{gormdb: db, sqldb: sql, credentialsManager: cm}, nil } // Close closes the database connection. @@ -83,7 +84,7 @@ func (d *Database) Populate(name, password, matrixID string) error { query := d.gormdb.Where("name = ?", name).First(&user) if errors.Is(query.Error, gorm.ErrRecordNotFound) { - user := model.NewUser(name, password, true, matrixID) + user := model.NewUser(d.credentialsManager, name, password, true, matrixID) if err := d.gormdb.Create(&user).Error; err != nil { return errors.New("user cannot be created") @@ -91,7 +92,7 @@ func (d *Database) Populate(name, password, matrixID string) error { } else { log.Printf("Admin user %s already exists.\n", name) - user.PasswordHash = credentials.CreatePasswordHash(password) + user.PasswordHash = d.credentialsManager.CreatePasswordHash(password) user.IsAdmin = true user.MatrixID = matrixID diff --git a/database/user.go b/database/user.go index 89d2247..af596cc 100644 --- a/database/user.go +++ b/database/user.go @@ -9,8 +9,10 @@ import ( ) // CreateUser creates a user. -func (d *Database) CreateUser(user *model.User) error { - return d.gormdb.Create(user).Error +func (d *Database) CreateUser(externalUser model.ExternalUserWithCredentials) (*model.User, error) { + user := externalUser.IntoInternalUser(d.credentialsManager) + + return user, d.gormdb.Create(user).Error } // DeleteUser deletes a user. diff --git a/model/user.go b/model/user.go index 1388663..0a4bd57 100644 --- a/model/user.go +++ b/model/user.go @@ -36,24 +36,22 @@ type ExternalUserWithCredentials struct { } // NewUser creates a new user. -func NewUser(name, password string, isAdmin bool, matrixID string) *User { +func NewUser(cm *credentials.Manager, name, password string, isAdmin bool, matrixID string) *User { log.Printf("Creating user %s.\n", name) - user := User{ + return &User{ Name: name, - PasswordHash: credentials.CreatePasswordHash(password), + PasswordHash: cm.CreatePasswordHash(password), IsAdmin: isAdmin, MatrixID: matrixID, } - - return &user } // IntoInternalUser converts a ExternalUserWithCredentials into a User. -func (u *ExternalUserWithCredentials) IntoInternalUser() *User { +func (u *ExternalUserWithCredentials) IntoInternalUser(cm *credentials.Manager) *User { return &User{ Name: u.Name, - PasswordHash: credentials.CreatePasswordHash(u.Password), + PasswordHash: cm.CreatePasswordHash(u.Password), IsAdmin: u.IsAdmin, MatrixID: u.MatrixID, }