fix: fist integration test #181
This commit was merged in pull request #189.
This commit is contained in:
136
auth_test.go
Normal file
136
auth_test.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"me-fit/service"
|
||||||
|
"me-fit/utils"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleSignIn(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("should signIn and return session cookie", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, done := context.WithCancel(context.Background())
|
||||||
|
t.Cleanup(done)
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite3", ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not open Database data.db: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
db.Close()
|
||||||
|
})
|
||||||
|
err = utils.RunMigrations(db, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not run migrations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pass := service.GetHashPassword("password", []byte("salt"))
|
||||||
|
_, err = db.Exec(`
|
||||||
|
INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at)
|
||||||
|
VALUES (?, "mail@mail.de", FALSE, FALSE, ?, ?, datetime())`, uuid.New(), pass, []byte("salt"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error inserting user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go run(ctx, db, func(key string) string {
|
||||||
|
if key == "PORT" {
|
||||||
|
return "8080"
|
||||||
|
} else if key == "SMTP_ENABLED" {
|
||||||
|
return "false"
|
||||||
|
} else if key == "PROMETHEUS_ENABLED" {
|
||||||
|
return "false"
|
||||||
|
} else if key == "BASE_URL" {
|
||||||
|
return "https://localhost:8080"
|
||||||
|
} else if key == "ENVIRONMENT" {
|
||||||
|
return "test"
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
err = waitForReady(ctx, 5*time.Second, "http://localhost:8080")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
formData := url.Values{
|
||||||
|
"email": {"mail@mail.de"},
|
||||||
|
"password": {"password"},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", "http://localhost:8080/api/auth/signin", strings.NewReader(formData.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the content type to application/x-www-form-urlencoded
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error making request: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("Expected status code 200, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForReady calls the specified endpoint until it gets a 200
|
||||||
|
// response or until the context is cancelled or the timeout is
|
||||||
|
// reached.
|
||||||
|
func waitForReady(
|
||||||
|
ctx context.Context,
|
||||||
|
timeout time.Duration,
|
||||||
|
endpoint string,
|
||||||
|
) error {
|
||||||
|
client := http.Client{}
|
||||||
|
startTime := time.Now()
|
||||||
|
for {
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
endpoint,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error making request: %s\n", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
fmt.Println("Endpoint is ready!")
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
if time.Since(startTime) >= timeout {
|
||||||
|
return fmt.Errorf("timeout reached while waiting for endpoint")
|
||||||
|
}
|
||||||
|
// wait a little while between checks
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,14 +18,14 @@ type User struct {
|
|||||||
Id uuid.UUID
|
Id uuid.UUID
|
||||||
Email string
|
Email string
|
||||||
EmailVerified bool
|
EmailVerified bool
|
||||||
EmailVerifiedAt time.Time
|
EmailVerifiedAt *time.Time
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
Password []byte
|
Password []byte
|
||||||
Salt []byte
|
Salt []byte
|
||||||
CreateAt time.Time
|
CreateAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(id uuid.UUID, email string, emailVerified bool, emailVerifiedAt time.Time, isAdmin bool, password []byte, salt []byte, createAt time.Time) *User {
|
func NewUser(id uuid.UUID, email string, emailVerified bool, emailVerifiedAt *time.Time, isAdmin bool, password []byte, salt []byte, createAt time.Time) *User {
|
||||||
return &User{
|
return &User{
|
||||||
Id: id,
|
Id: id,
|
||||||
Email: email,
|
Email: email,
|
||||||
@@ -54,7 +54,7 @@ func (db DbAuthSqlite) GetUser(email string) (*User, error) {
|
|||||||
var (
|
var (
|
||||||
userId uuid.UUID
|
userId uuid.UUID
|
||||||
emailVerified bool
|
emailVerified bool
|
||||||
emailVerifiedAt time.Time
|
emailVerifiedAt *time.Time
|
||||||
isAdmin bool
|
isAdmin bool
|
||||||
password []byte
|
password []byte
|
||||||
salt []byte
|
salt []byte
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ func setupDb(t *testing.T) *sql.DB {
|
|||||||
t.Fatalf("Error opening database: %v", err)
|
t.Fatalf("Error opening database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.MustRunMigrations(db, "../")
|
err = utils.RunMigrations(db, "../")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error running migrations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
@@ -46,7 +49,7 @@ func TestGetUser(t *testing.T) {
|
|||||||
|
|
||||||
verifiedAt := time.Date(2020, 1, 5, 13, 0, 0, 0, time.UTC)
|
verifiedAt := time.Date(2020, 1, 5, 13, 0, 0, 0, time.UTC)
|
||||||
createAt := time.Date(2020, 1, 5, 12, 0, 0, 0, time.UTC)
|
createAt := time.Date(2020, 1, 5, 12, 0, 0, 0, time.UTC)
|
||||||
user := NewUser(uuid.New(), "some@email.de", true, verifiedAt, false, []byte("somePass"), []byte("someSalt"), createAt)
|
user := NewUser(uuid.New(), "some@email.de", true, &verifiedAt, false, []byte("somePass"), []byte("someSalt"), createAt)
|
||||||
|
|
||||||
_, err := db.Exec(`
|
_, err := db.Exec(`
|
||||||
INSERT INTO user (user_uuid, email, email_verified, email_verified_at, is_admin, password, salt, created_at)
|
INSERT INTO user (user_uuid, email, email_verified, email_verified_at, is_admin, password, salt, created_at)
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHandleSignIn(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
t.Run("should signIn and return session cookie", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
18
main.go
18
main.go
@@ -27,10 +27,16 @@ func main() {
|
|||||||
log.Fatal("Error loading .env file")
|
log.Fatal("Error loading .env file")
|
||||||
}
|
}
|
||||||
|
|
||||||
run(context.Background(), os.Getenv)
|
db, err := sql.Open("sqlite3", "./data.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Could not open Database data.db: ", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
run(context.Background(), db, os.Getenv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(ctx context.Context, env func(string) string) {
|
func run(ctx context.Context, db *sql.DB, env func(string) string) {
|
||||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -40,15 +46,13 @@ func run(ctx context.Context, env func(string) string) {
|
|||||||
serverSettings := types.NewServerSettingsFromEnv(env)
|
serverSettings := types.NewServerSettingsFromEnv(env)
|
||||||
|
|
||||||
// init db
|
// init db
|
||||||
db, err := sql.Open("sqlite3", serverSettings.DbPath)
|
err := utils.RunMigrations(db, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not open Database data.db: ", err)
|
slog.Error("Could not run migrations: " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
|
||||||
utils.MustRunMigrations(db, "")
|
|
||||||
|
|
||||||
// init servers
|
// init servers
|
||||||
|
|
||||||
var prometheusServer *http.Server
|
var prometheusServer *http.Server
|
||||||
if serverSettings.PrometheusEnabled {
|
if serverSettings.PrometheusEnabled {
|
||||||
prometheusServer := &http.Server{
|
prometheusServer := &http.Server{
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (service ServiceAuthImpl) SignIn(email string, password string) (*User, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := getHashPassword(password, user.Salt)
|
hash := GetHashPassword(password, user.Salt)
|
||||||
|
|
||||||
if subtle.ConstantTimeCompare(hash, user.Password) == 0 {
|
if subtle.ConstantTimeCompare(hash, user.Password) == 0 {
|
||||||
return nil, ErrInvaidCredentials
|
return nil, ErrInvaidCredentials
|
||||||
@@ -279,7 +279,7 @@ func HandleSignUpComp(db *sql.DB, serverSettings *types.ServerSettings) http.Han
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := getHashPassword(password, salt)
|
hash := GetHashPassword(password, salt)
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", userId, email, hash, salt)
|
_, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", userId, email, hash, salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -366,7 +366,7 @@ func HandleDeleteAccountComp(db *sql.DB, serverSettings *types.ServerSettings) h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currHash := getHashPassword(password, salt)
|
currHash := GetHashPassword(password, salt)
|
||||||
if subtle.ConstantTimeCompare(currHash, storedHash) == 0 {
|
if subtle.ConstantTimeCompare(currHash, storedHash) == 0 {
|
||||||
utils.TriggerToast(w, r, "error", "Password is not correct")
|
utils.TriggerToast(w, r, "error", "Password is not correct")
|
||||||
return
|
return
|
||||||
@@ -455,13 +455,13 @@ func HandleChangePasswordComp(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currHash := getHashPassword(currPass, salt)
|
currHash := GetHashPassword(currPass, salt)
|
||||||
if subtle.ConstantTimeCompare(currHash, storedHash) == 0 {
|
if subtle.ConstantTimeCompare(currHash, storedHash) == 0 {
|
||||||
utils.TriggerToast(w, r, "error", "Current Password is not correct")
|
utils.TriggerToast(w, r, "error", "Current Password is not correct")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newHash := getHashPassword(newPass, salt)
|
newHash := GetHashPassword(newPass, salt)
|
||||||
|
|
||||||
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", newHash, user.Id)
|
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", newHash, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -524,7 +524,7 @@ func HandleActualResetPasswordComp(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
passHash := getHashPassword(newPass, salt)
|
passHash := GetHashPassword(newPass, salt)
|
||||||
|
|
||||||
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", passHash, userId)
|
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", passHash, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -653,7 +653,7 @@ func TryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sq
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHashPassword(password string, salt []byte) []byte {
|
func GetHashPassword(password string, salt []byte) []byte {
|
||||||
return argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16)
|
return argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,15 @@ func TestSignIn(t *testing.T) {
|
|||||||
t.Run("should return user if password is correct", func(t *testing.T) {
|
t.Run("should return user if password is correct", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
salt := []byte("salt")
|
salt := []byte("salt")
|
||||||
|
verifiedAt := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
|
||||||
stub := DbAuthStub{
|
stub := DbAuthStub{
|
||||||
user: db.NewUser(
|
user: db.NewUser(
|
||||||
uuid.New(),
|
uuid.New(),
|
||||||
"test@test.de",
|
"test@test.de",
|
||||||
true,
|
true,
|
||||||
time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC),
|
&verifiedAt,
|
||||||
false,
|
false,
|
||||||
getHashPassword("password", salt),
|
GetHashPassword("password", salt),
|
||||||
salt,
|
salt,
|
||||||
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
),
|
),
|
||||||
@@ -58,14 +59,15 @@ func TestSignIn(t *testing.T) {
|
|||||||
t.Run("should return ErrInvalidCretentials if password is not correct", func(t *testing.T) {
|
t.Run("should return ErrInvalidCretentials if password is not correct", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
salt := []byte("salt")
|
salt := []byte("salt")
|
||||||
|
verifiedAt := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
|
||||||
stub := DbAuthStub{
|
stub := DbAuthStub{
|
||||||
user: db.NewUser(
|
user: db.NewUser(
|
||||||
uuid.New(),
|
uuid.New(),
|
||||||
"test@test.de",
|
"test@test.de",
|
||||||
true,
|
true,
|
||||||
time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC),
|
&verifiedAt,
|
||||||
false,
|
false,
|
||||||
getHashPassword("password", salt),
|
GetHashPassword("password", salt),
|
||||||
salt,
|
salt,
|
||||||
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ type ServerSettings struct {
|
|||||||
|
|
||||||
BaseUrl string
|
BaseUrl string
|
||||||
Environment string
|
Environment string
|
||||||
DbPath string
|
|
||||||
Smtp *SmtpSettings
|
Smtp *SmtpSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,15 +60,10 @@ func NewServerSettingsFromEnv(env func(string) string) *ServerSettings {
|
|||||||
Port: env("PORT"),
|
Port: env("PORT"),
|
||||||
PrometheusEnabled: env("PROMETHEUS_ENABLED") == "true",
|
PrometheusEnabled: env("PROMETHEUS_ENABLED") == "true",
|
||||||
BaseUrl: env("BASE_URL"),
|
BaseUrl: env("BASE_URL"),
|
||||||
DbPath: env("DB_PATH"),
|
|
||||||
Environment: env("ENVIRONMENT"),
|
Environment: env("ENVIRONMENT"),
|
||||||
Smtp: smtp,
|
Smtp: smtp,
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.DbPath == "" {
|
|
||||||
settings.DbPath = "./data.db"
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.BaseUrl == "" {
|
if settings.BaseUrl == "" {
|
||||||
log.Fatal("BASE_URL must be set")
|
log.Fatal("BASE_URL must be set")
|
||||||
}
|
}
|
||||||
|
|||||||
19
utils/db.go
19
utils/db.go
@@ -2,17 +2,20 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"me-fit/types"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MustRunMigrations(db *sql.DB, pathPrefix string) {
|
func RunMigrations(db *sql.DB, pathPrefix string) error {
|
||||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Could not create Migration instance: " + err.Error())
|
||||||
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := migrate.NewWithDatabaseInstance(
|
m, err := migrate.NewWithDatabaseInstance(
|
||||||
@@ -20,13 +23,17 @@ func MustRunMigrations(db *sql.DB, pathPrefix string) {
|
|||||||
"",
|
"",
|
||||||
driver)
|
driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not create migrations instance: ", err)
|
slog.Error("Could not create migrations instance: " + err.Error())
|
||||||
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.Up()
|
err = m.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != "no change" {
|
if !errors.Is(err, migrate.ErrNoChange) {
|
||||||
log.Fatal("Could not run migrations: ", err)
|
slog.Error("Could not run migrations: " + err.Error())
|
||||||
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user