auth services, middleware, and other stuff
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

This commit is contained in:
2025-05-22 13:55:43 -04:00
parent 70bb4e66b4
commit e55d419d44
22 changed files with 985 additions and 95 deletions

View File

@@ -1,6 +1,7 @@
package repository
import (
"bytes"
"context"
"errors"
"log/slog"
@@ -17,15 +18,12 @@ var (
type UserRow struct {
Id uuid.UUID
Email string
HashedPassword string
}
func NewUserRow(id uuid.UUID, email string, hashedPassword string) UserRow {
return UserRow{Id: id, Email: email, HashedPassword: hashedPassword}
HashedPassword []byte
Salt []byte
}
func (u UserRow) Equal(user UserRow) bool {
return u.Id == user.Id && u.Email == user.Email && u.HashedPassword == user.HashedPassword
return u.Id == user.Id && u.Email == user.Email && bytes.Equal(u.HashedPassword, user.HashedPassword) && bytes.Equal(u.Salt, user.Salt)
}
type UserRepository struct {
@@ -43,7 +41,24 @@ func NewUserRepository(logger *slog.Logger, db *pgxpool.Pool) *UserRepository {
func (r *UserRepository) GetById(ctx context.Context, id uuid.UUID) (UserRow, error) {
user := UserRow{}
err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1;", id).Scan(&user.Id, &user.Email, &user.HashedPassword)
err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1;", id).Scan(&user.Id, &user.Email, &user.HashedPassword, &user.Salt)
if err != nil {
if err == pgx.ErrNoRows {
return user, ErrNotFound
}
r.logger.ErrorContext(ctx, err.Error())
return user, err
}
return user, nil
}
func (r *UserRepository) GetByEmail(ctx context.Context, email string) (UserRow, error) {
user := UserRow{}
err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE email = $1;", email).Scan(&user.Id, &user.Email, &user.HashedPassword, &user.Salt)
if err != nil {
if err == pgx.ErrNoRows {
@@ -60,7 +75,7 @@ func (r *UserRepository) GetById(ctx context.Context, id uuid.UUID) (UserRow, er
func (r *UserRepository) Create(ctx context.Context, user UserRow) (UserRow, error) {
var result pgx.Row
if user.Id.IsNil() {
result = r.db.QueryRow(ctx, "INSERT INTO users (email, hashed_password) VALUES ($1, $2) RETURNING id;", user.Email, user.HashedPassword)
result = r.db.QueryRow(ctx, "INSERT INTO users (email, hashed_password, salt) VALUES ($1, $2, $3) RETURNING id;", user.Email, user.HashedPassword, user.Salt)
err := result.Scan(&user.Id)
@@ -69,7 +84,7 @@ func (r *UserRepository) Create(ctx context.Context, user UserRow) (UserRow, err
return UserRow{}, err
}
} else {
_, err := r.db.Exec(ctx, "INSERT INTO users (id, email, hashed_password) VALUES ($1, $2, $3);", user.Id, user.Email, user.HashedPassword)
_, err := r.db.Exec(ctx, "INSERT INTO users (id, email, hashed_password, salt) VALUES ($1, $2, $3, $4);", user.Id, user.Email, user.HashedPassword, user.Salt)
if err != nil {
r.logger.ErrorContext(ctx, err.Error())
@@ -81,7 +96,7 @@ func (r *UserRepository) Create(ctx context.Context, user UserRow) (UserRow, err
}
func (r *UserRepository) Update(ctx context.Context, user UserRow) error {
result, err := r.db.Exec(ctx, "UPDATE users SET email = $1, hashed_password = $2 WHERE id = $3;", user.Email, user.HashedPassword, user.Id)
result, err := r.db.Exec(ctx, "UPDATE users SET email = $1, hashed_password = $2, salt = $3 WHERE id = $4;", user.Email, user.HashedPassword, user.Salt, user.Id)
if err != nil {
r.logger.ErrorContext(ctx, err.Error())

View File

@@ -6,6 +6,7 @@ import (
"log/slog"
"testing"
"gitea.michaelthomson.dev/mthomson/habits/internal/auth"
"gitea.michaelthomson.dev/mthomson/habits/internal/test"
"gitea.michaelthomson.dev/mthomson/habits/internal/user/repository"
"github.com/gofrs/uuid/v5"
@@ -18,22 +19,36 @@ func TestCRUD(t *testing.T) {
defer tdb.TearDown()
r := repository.NewUserRepository(logger, tdb.Db)
uuid := NewUUID(t)
argon2IdHash := auth.NewArgon2IdHash(1, 32, 64*1024, 32, 256)
hashSalt, err := argon2IdHash.GenerateHash([]byte("supersecurepassword"), []byte("supersecuresalt"))
if err != nil {
t.Errorf("could not generate hash: %v", err)
}
t.Run("creates new user", func(t *testing.T) {
newUser := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
newUser := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: hashSalt.Hash, Salt: hashSalt.Salt}
_, err := r.Create(ctx, newUser)
AssertNoError(t, err)
})
t.Run("gets user", func(t *testing.T) {
want := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: "supersecurehash"}
t.Run("gets user by id", func(t *testing.T) {
want := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: hashSalt.Hash, Salt: hashSalt.Salt}
got, err := r.GetById(ctx, uuid)
AssertNoError(t, err)
AssertUserRows(t, got, want)
})
t.Run("gets user by email", func(t *testing.T) {
want := repository.UserRow{Id: uuid, Email: "test@test.com", HashedPassword: hashSalt.Hash, Salt: hashSalt.Salt}
got, err := r.GetByEmail(ctx, "test@test.com")
AssertNoError(t, err)
AssertUserRows(t, got, want)
})
t.Run("updates user", func(t *testing.T) {
want := repository.UserRow{Id: uuid, Email: "new@test.com", HashedPassword: "supersecurehash"}
want := repository.UserRow{Id: uuid, Email: "new@test.com", HashedPassword: hashSalt.Hash, Salt: hashSalt.Salt}
err := r.Update(ctx, want)
AssertNoError(t, err)