From 7a9d34d46464ff1e48147d608837fe494d75d0a5 Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Mon, 23 Dec 2024 13:54:09 +0100 Subject: [PATCH] chore(auth): #331 add tests for sign in --- handler/auth.go | 4 +- main_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ utils/http.go | 4 +- 3 files changed, 136 insertions(+), 4 deletions(-) diff --git a/handler/auth.go b/handler/auth.go index a448f04..43e8a36 100644 --- a/handler/auth.go +++ b/handler/auth.go @@ -31,8 +31,8 @@ func NewAuth(service service.Auth, render *Render) Auth { } func (handler AuthImpl) Handle(router *http.ServeMux) { - router.Handle("/auth/signin", handler.handleSignInPage()) - router.Handle("/api/auth/signin", handler.handleSignIn()) + router.Handle("GET /auth/signin", handler.handleSignInPage()) + router.Handle("POST /api/auth/signin", handler.handleSignIn()) router.Handle("/auth/signup", handler.handleSignUpPage()) router.Handle("/auth/verify", handler.handleSignUpVerifyPage()) diff --git a/main_test.go b/main_test.go index 765efc7..a15efdb 100644 --- a/main_test.go +++ b/main_test.go @@ -113,6 +113,138 @@ func TestIntegrationAuth(t *testing.T) { t.Parallel() t.Run("SignIn", func(t *testing.T) { + t.Run(`should redirect to "/" if user is already signed in`, func(t *testing.T) { + t.Parallel() + + db, basePath, ctx := setupIntegrationTest(t) + + userId := uuid.New() + sessionId := "session-id" + pass := service.GetHashPassword("password", []byte("salt")) + + _, err := db.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", TRUE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + _, err = db.Exec(` + INSERT INTO session (session_id, user_id, created_at, expires_at) + VALUES (?, ?, datetime(), datetime("now", "+1 day"))`, sessionId, userId) + assert.Nil(t, err) + + req, err := http.NewRequestWithContext(ctx, "GET", basePath+"/auth/signin", nil) + assert.Nil(t, err) + req.Header.Set("Cookie", "id="+sessionId) + resp, err := httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusSeeOther, resp.StatusCode) + assert.Equal(t, "/", resp.Header.Get("Location")) + }) + t.Run(`should fail without valid csrf token`, func(t *testing.T) { + t.Parallel() + + db, basePath, ctx := setupIntegrationTest(t) + + userId := uuid.New() + pass := service.GetHashPassword("password", []byte("salt")) + + _, err := db.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", TRUE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + + formData := url.Values{ + "email": {"mail@mail.de"}, + "password": {"password"}, + "csrf-token": {"invalid-csrf-token"}, + } + + req, err := http.NewRequestWithContext(ctx, "POST", basePath+"/api/auth/signin", strings.NewReader(formData.Encode())) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + t.Run(`should fail with invalid username`, func(t *testing.T) { + t.Parallel() + + db, basePath, ctx := setupIntegrationTest(t) + + userId := uuid.New() + pass := service.GetHashPassword("password", []byte("salt")) + + _, err := db.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", TRUE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + + req, err := http.NewRequestWithContext(ctx, "GET", basePath+"/auth/signin", nil) + assert.Nil(t, err) + resp, err := httpClient.Do(req) + assert.Nil(t, err) + + html, err := html.Parse(resp.Body) + assert.Nil(t, err) + csrfToken := findCsrfToken(html) + assert.NotEqual(t, "", csrfToken) + session := findCookie(resp, "id") + + formData := url.Values{ + "email": {"invalid@mail.de"}, + "password": {"password"}, + "csrf-token": {csrfToken}, + } + + req, err = http.NewRequestWithContext(ctx, "POST", basePath+"/api/auth/signin", strings.NewReader(formData.Encode())) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cookie", "id="+session.Value) + req.Header.Set("HX-Request", "true") + resp, err = httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + assert.Contains(t, resp.Header.Get("HX-Trigger"), "email or password") + }) + t.Run(`should fail with invalid password`, func(t *testing.T) { + t.Parallel() + + db, basePath, ctx := setupIntegrationTest(t) + + userId := uuid.New() + pass := service.GetHashPassword("password", []byte("salt")) + + _, err := db.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", TRUE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + + req, err := http.NewRequestWithContext(ctx, "GET", basePath+"/auth/signin", nil) + assert.Nil(t, err) + resp, err := httpClient.Do(req) + assert.Nil(t, err) + + html, err := html.Parse(resp.Body) + assert.Nil(t, err) + csrfToken := findCsrfToken(html) + assert.NotEqual(t, "", csrfToken) + session := findCookie(resp, "id") + + formData := url.Values{ + "email": {"mail@mail.de"}, + "password": {"invalid-password"}, + "csrf-token": {csrfToken}, + } + + req, err = http.NewRequestWithContext(ctx, "POST", basePath+"/api/auth/signin", strings.NewReader(formData.Encode())) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cookie", "id="+session.Value) + req.Header.Set("HX-Request", "true") + resp, err = httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + assert.Contains(t, resp.Header.Get("HX-Trigger"), "email or password") + }) t.Run("should return secure cookie with NEW session-id", func(t *testing.T) { t.Parallel() diff --git a/utils/http.go b/utils/http.go index f2f692d..f2f63fe 100644 --- a/utils/http.go +++ b/utils/http.go @@ -1,11 +1,11 @@ package utils import ( - "me-fit/log" - "fmt" "net/http" "time" + + "me-fit/log" ) func TriggerToast(w http.ResponseWriter, r *http.Request, class string, message string, statusCode int) {