From 0143b6e4cb7c31abee7d71a2dbd713abcc98936d Mon Sep 17 00:00:00 2001 From: xengineering Date: Fri, 5 Jun 2026 22:45:01 +0200 Subject: Add Register() This function creates a new user and sets the username and bcrypt-hashed password. --- meson.build | 1 + password.go | 26 ++++++++++++++++++++++++++ password_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 password.go create mode 100644 password_test.go diff --git a/meson.build b/meson.build index ba66106..7955190 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ finserv_linux_amd64 = custom_target( meson.current_source_dir() / 'main.go', meson.current_source_dir() / 'handlers.go', meson.current_source_dir() / 'database.go', + meson.current_source_dir() / 'password.go', ], output : 'finserv-linux-amd64', env : {'GOOS': 'linux', 'GOARCH': 'amd64'}, diff --git a/password.go b/password.go new file mode 100644 index 0000000..ae11968 --- /dev/null +++ b/password.go @@ -0,0 +1,26 @@ +package main + +import ( + "database/sql" + + "golang.org/x/crypto/bcrypt" + _ "github.com/mattn/go-sqlite3" +) + +const ( + bcryptCost = 12 +) + +func Register(db *sql.DB, username, password string) error { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) + if err != nil { + return err + } + + err = Transaction(db, func(tx *sql.Tx) error { + _, err := tx.Exec(`INSERT INTO users (username, password_bcrypt) VALUES(?, ?);`, username, hash) + return err + }) + + return err +} diff --git a/password_test.go b/password_test.go new file mode 100644 index 0000000..f540cc4 --- /dev/null +++ b/password_test.go @@ -0,0 +1,57 @@ +package main + +import ( + "testing" + + "golang.org/x/crypto/bcrypt" + _ "github.com/mattn/go-sqlite3" +) + +var passwords = []struct { + description string + username string + password string +}{ + {"basic", "testuser", "mypassword"}, + {"password-characterset", "testuser", "mypSDFäÖ$🚧"}, + {"user-characterset", "mypSDFäÖ$🚧", "mypassword"}, +} + +func TestRegister(t *testing.T) { + for _, p := range passwords { + t.Run(p.description, func(t *testing.T) { + db, _ := emptyDB(t) + + err := MigrateToLatest(db) + if err != nil { + t.Fatalf("Could not execute function to test: %v", err) + } + + err = Register(db, p.username, p.password) + if err != nil { + t.Fatalf("Could not register user: %v", err) + } + + row := db.QueryRow( + `SELECT password_bcrypt FROM users WHERE username=?;`, + p.username, + ) + + var hash []byte + err = row.Scan(&hash) + if err != nil { + t.Fatalf("Could not scan for password: %v", err) + } + + err = bcrypt.CompareHashAndPassword(hash, []byte(p.password)) + if err != nil { + t.Fatalf("Password not valid after registration: %v", err) + } + + err = bcrypt.CompareHashAndPassword(hash, []byte("")) + if err == nil { + t.Fatalf("Empty password is valid after registration.") + } + }) + } +} -- cgit v1.3.1