From 55408da3986900e8df54e052cf346cb8b9ec0054 Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Wed, 25 Dec 2024 23:12:31 +0100 Subject: [PATCH] chore(auth): #331 add and fix forgot password actual tests --- handler/auth.go | 2 +- main_test.go | 135 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/handler/auth.go b/handler/auth.go index 2a049fc..ccf9e52 100644 --- a/handler/auth.go +++ b/handler/auth.go @@ -374,7 +374,7 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc { err = handler.service.ForgotPassword(token, newPass) if err != nil { - utils.TriggerToast(w, r, "error", err.Error(), http.StatusInternalServerError) + utils.TriggerToast(w, r, "error", err.Error(), http.StatusBadRequest) } else { utils.TriggerToast(w, r, "success", "Password changed", http.StatusOK) } diff --git a/main_test.go b/main_test.go index b2b61bb..cf1e9c3 100644 --- a/main_test.go +++ b/main_test.go @@ -1338,6 +1338,141 @@ func TestIntegrationAuth(t *testing.T) { }) t.Run("ForgotPasswordResponse", func(t *testing.T) { + t.Run(`should fail if token does not exist`, func(t *testing.T) { + t.Parallel() + + d, basePath, ctx := setupIntegrationTest(t) + userId := uuid.New() + + pass := service.GetHashPassword("password", []byte("salt")) + _, err := d.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", FALSE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + + req, err := http.NewRequestWithContext(ctx, "GET", basePath+"/auth/forgot-password", nil) + assert.Nil(t, err) + resp, err := httpClient.Do(req) + assert.Nil(t, err) + anonymousSessionId := findCookie(resp, "id").Value + html, err := html.Parse(resp.Body) + assert.Nil(t, err) + anonymousCsrfToken := findCsrfToken(html) + assert.NotEqual(t, "", anonymousCsrfToken) + + formData := url.Values{ + "new-password": {"MyNewSecurePassword1!"}, + "csrf-token": {anonymousCsrfToken}, + } + req, err = http.NewRequestWithContext(ctx, "POST", basePath+"/api/auth/forgot-password-actual", strings.NewReader(formData.Encode())) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cookie", "id="+anonymousSessionId) + req.Header.Set("HX-Request", "true") + req.Header.Set("HX-Current-URL", basePath+"/auth/change-password?token=invalidToken") + resp, err = httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var rows int + err = d.QueryRow("SELECT COUNT(*) FROM user WHERE user_id = ? AND password = ?", userId, pass).Scan(&rows) + assert.Nil(t, err) + assert.Equal(t, 1, rows) + }) + t.Run(`should fail if token is outdated`, func(t *testing.T) { + t.Parallel() + + d, basePath, ctx := setupIntegrationTest(t) + userId := uuid.New() + + pass := service.GetHashPassword("password", []byte("salt")) + _, err := d.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", FALSE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + + req, err := http.NewRequestWithContext(ctx, "GET", basePath+"/auth/forgot-password", nil) + assert.Nil(t, err) + resp, err := httpClient.Do(req) + assert.Nil(t, err) + anonymousSessionId := findCookie(resp, "id").Value + html, err := html.Parse(resp.Body) + assert.Nil(t, err) + anonymousCsrfToken := findCsrfToken(html) + assert.NotEqual(t, "", anonymousCsrfToken) + + token := "password-reset-token" + _, err = d.Exec(` + INSERT INTO token (token, user_id, session_id, type, created_at, expires_at) + VALUES (?, ?, ?, ?, datetime("now", "-16 minute"), datetime("now", "-1 minute"))`, token, userId, "", types.TokenTypePasswordReset) + assert.Nil(t, err) + + formData := url.Values{ + "new-password": {"MyNewSecurePassword1!"}, + "csrf-token": {anonymousCsrfToken}, + } + req, err = http.NewRequestWithContext(ctx, "POST", basePath+"/api/auth/forgot-password-actual", strings.NewReader(formData.Encode())) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cookie", "id="+anonymousSessionId) + req.Header.Set("HX-Request", "true") + req.Header.Set("HX-Current-URL", basePath+"/auth/change-password?token="+url.QueryEscape(token)) + resp, err = httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var rows int + err = d.QueryRow("SELECT COUNT(*) FROM user WHERE user_id = ? AND password = ?", userId, pass).Scan(&rows) + assert.Nil(t, err) + assert.Equal(t, 1, rows) + }) + t.Run(`should fail if password is insecure`, func(t *testing.T) { + t.Parallel() + + d, basePath, ctx := setupIntegrationTest(t) + userId := uuid.New() + + pass := service.GetHashPassword("password", []byte("salt")) + _, err := d.Exec(` + INSERT INTO user (user_id, email, email_verified, is_admin, password, salt, created_at) + VALUES (?, "mail@mail.de", FALSE, FALSE, ?, ?, datetime())`, userId, pass, []byte("salt")) + assert.Nil(t, err) + + req, err := http.NewRequestWithContext(ctx, "GET", basePath+"/auth/forgot-password", nil) + assert.Nil(t, err) + resp, err := httpClient.Do(req) + assert.Nil(t, err) + anonymousSessionId := findCookie(resp, "id").Value + html, err := html.Parse(resp.Body) + assert.Nil(t, err) + anonymousCsrfToken := findCsrfToken(html) + assert.NotEqual(t, "", anonymousCsrfToken) + + token := "password-reset-token" + _, err = d.Exec(` + INSERT INTO token (token, user_id, session_id, type, created_at, expires_at) + VALUES (?, ?, ?, ?, datetime("now"), datetime("now", "+15 minute"))`, token, userId, "", types.TokenTypePasswordReset) + assert.Nil(t, err) + + formData := url.Values{ + "new-password": {"insecure-password"}, + "csrf-token": {anonymousCsrfToken}, + } + req, err = http.NewRequestWithContext(ctx, "POST", basePath+"/api/auth/forgot-password-actual", strings.NewReader(formData.Encode())) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cookie", "id="+anonymousSessionId) + req.Header.Set("HX-Request", "true") + req.Header.Set("HX-Current-URL", basePath+"/auth/change-password?token="+url.QueryEscape(token)) + resp, err = httpClient.Do(req) + assert.Nil(t, err) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + + var rows int + err = d.QueryRow("SELECT COUNT(*) FROM user WHERE user_id = ? AND password = ?", userId, pass).Scan(&rows) + assert.Nil(t, err) + assert.Equal(t, 1, rows) + }) t.Run("should change password and invalidate ALL sessions", func(t *testing.T) { t.Parallel()