diff options
| author | xengineering <me@xengineering.eu> | 2026-06-27 21:21:45 +0200 |
|---|---|---|
| committer | xengineering <me@xengineering.eu> | 2026-06-27 21:21:45 +0200 |
| commit | 132990e985751e40c263224a62983c8013409a36 (patch) | |
| tree | f4952cde44a950bfe7c0bdfec825056f85494b69 | |
| parent | ea4bd88963724c13f7b906d856af2d1c715fc056 (diff) | |
| download | finserv-132990e985751e40c263224a62983c8013409a36.tar finserv-132990e985751e40c263224a62983c8013409a36.tar.zst finserv-132990e985751e40c263224a62983c8013409a36.zip | |
Switch Register() and Authenticate() to sql.Tx
Using sql.Tx (database transactions) instead of a plain database has the
advantage that the caller can wrap multiple library calls into one
transaction.
This guarantees that no database entries change between the library
calls which is critical to prevent race conditions.
| -rw-r--r-- | handlers.go | 16 | ||||
| -rw-r--r-- | handlers_test.go | 4 | ||||
| -rw-r--r-- | password.go | 30 | ||||
| -rw-r--r-- | password_test.go | 17 |
4 files changed, 39 insertions, 28 deletions
diff --git a/handlers.go b/handlers.go index 0ff2f26..4b86ad5 100644 --- a/handlers.go +++ b/handlers.go @@ -62,11 +62,15 @@ func ApiAuthentication(db *sql.DB) http.Handler { return } - err = Authenticate(db, data.Username, data.Password) - if err != nil { - log.Printf("Bad password on authentication of user '%s'.", data.Username) - w.WriteHeader(http.StatusUnauthorized) - return - } + _ = Transaction(db, func(tx *sql.Tx) error { + err := Authenticate(tx, data.Username, data.Password) + if err != nil { + log.Printf("Bad password on authentication of user '%s'.", data.Username) + w.WriteHeader(http.StatusUnauthorized) + return fmt.Errorf("Authentication failed.") + } + + return nil + }) }) } diff --git a/handlers_test.go b/handlers_test.go index 78baac6..c4aba29 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -83,7 +83,9 @@ func TestApiAuthenticate(t *testing.T) { t.Fatalf("Could not migrate test database: %v", err) } - err = Register(db, "testuser", "mypass") + err = Transaction(db, func(tx *sql.Tx) error { + return Register(tx, "testuser", "mypass") + }) if err != nil { t.Fatalf("Failed to register test user: %v", err) } diff --git a/password.go b/password.go index edbeb7a..9be347a 100644 --- a/password.go +++ b/password.go @@ -12,34 +12,30 @@ const ( bcryptCost = 12 ) -func Register(db *sql.DB, username, password string) error { +func Register(tx *sql.Tx, 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 - }) + _, err = tx.Exec( + `INSERT INTO users (username, password_bcrypt) VALUES(?, ?);`, + username, + hash, + ) return err } -func Authenticate(db *sql.DB, username, password string) error { +func Authenticate(tx *sql.Tx, username, password string) error { var hash []byte - err := Transaction(db, func(tx *sql.Tx) error { - row := tx.QueryRow( - `SELECT password_bcrypt from users WHERE username=?;`, - username, - ) - return row.Scan(&hash) - }) + row := tx.QueryRow( + `SELECT password_bcrypt from users WHERE username=?;`, + username, + ) + + err := row.Scan(&hash) if err != nil { return fmt.Errorf("Failed to get password hash from database: %w", err) } diff --git a/password_test.go b/password_test.go index a577194..ed89541 100644 --- a/password_test.go +++ b/password_test.go @@ -1,6 +1,7 @@ package main import ( + "database/sql" "testing" _ "github.com/mattn/go-sqlite3" @@ -27,7 +28,9 @@ func TestRegister(t *testing.T) { t.Fatalf("Could not migrate test database: %v", err) } - err = Register(db, p.username, p.password) + err = Transaction(db, func(tx *sql.Tx) error { + return Register(tx, p.username, p.password) + }) if err != nil { t.Fatalf("Could not register user: %v", err) } @@ -80,7 +83,9 @@ func TestAuthenticate(t *testing.T) { t.Fatalf("Failed to insert password hash: %v", err) } - err = Authenticate(db, p.username, p.password) + err = Transaction(db, func(tx *sql.Tx) error { + return Authenticate(tx, p.username, p.password) + }) if err != nil { t.Fatalf("Failed to authenticate: %v", err) } @@ -98,12 +103,16 @@ func TestRegisterAuthenticate(t *testing.T) { t.Fatalf("Could not execute function to test: %v", err) } - err = Register(db, p.username, p.password) + err = Transaction(db, func(tx *sql.Tx) error { + return Register(tx, p.username, p.password) + }) if err != nil { t.Fatalf("Could not register user: %v", err) } - err = Authenticate(db, p.username, p.password) + err = Transaction(db, func(tx *sql.Tx) error { + return Authenticate(tx, p.username, p.password) + }) if err != nil { t.Fatalf("Failed to authenticate: %v", err) } |
