summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2026-06-27 21:21:45 +0200
committerxengineering <me@xengineering.eu>2026-06-27 21:21:45 +0200
commit132990e985751e40c263224a62983c8013409a36 (patch)
treef4952cde44a950bfe7c0bdfec825056f85494b69
parentea4bd88963724c13f7b906d856af2d1c715fc056 (diff)
downloadfinserv-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.go16
-rw-r--r--handlers_test.go4
-rw-r--r--password.go30
-rw-r--r--password_test.go17
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)
}