From 9a0a6f88e333d02617fc028f257beb927c84ec43 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 17 Mar 2026 04:58:20 -0700 Subject: [PATCH] feat: redirect root path based on auth state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit / now checks for an authenticated session: - Authenticated users → 303 redirect to /sources - Unauthenticated users → 303 redirect to /pages/login Removes the old index page template rendering and formatUptime helper (now dead code). Adds tests for both redirect cases. --- README.md | 2 +- internal/handlers/handlers.go | 1 - internal/handlers/handlers_test.go | 79 ++++++++++++++---------------- internal/handlers/index.go | 47 ++++-------------- 4 files changed, 48 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 50faf6c..6bfe76a 100644 --- a/README.md +++ b/README.md @@ -652,7 +652,7 @@ against a misbehaving sender). | Method | Path | Description | | ------ | --------------------------- | ----------- | -| `GET` | `/` | Web UI index page (server-rendered) | +| `GET` | `/` | Root redirect (authenticated → `/sources`, unauthenticated → `/pages/login`) | | `GET` | `/.well-known/healthcheck` | Health check (JSON: status, uptime, version) | | `GET` | `/s/*` | Static file serving (embedded CSS, JS) | | `ANY` | `/webhook/{uuid}` | Webhook receiver endpoint (accepts all methods) | diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 47915f3..2f49565 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -65,7 +65,6 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) { // Parse all page templates once at startup s.templates = map[string]*template.Template{ - "index.html": parsePageTemplate("index.html"), "login.html": parsePageTemplate("login.html"), "profile.html": parsePageTemplate("profile.html"), "sources_list.html": parsePageTemplate("sources_list.html"), diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 7f11f43..0d2ba7b 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -4,10 +4,8 @@ import ( "net/http" "net/http/httptest" "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" "sneak.berlin/go/webhooker/internal/config" @@ -26,6 +24,7 @@ func (n *noopNotifier) Notify([]delivery.DeliveryTask) {} func TestHandleIndex(t *testing.T) { var h *Handlers + var sess *session.Session app := fxtest.New( t, @@ -44,15 +43,47 @@ func TestHandleIndex(t *testing.T) { func() delivery.Notifier { return &noopNotifier{} }, New, ), - fx.Populate(&h), + fx.Populate(&h, &sess), ) app.RequireStart() defer app.RequireStop() - // Since we can't test actual template rendering without templates, - // let's test that the handler is created and doesn't panic - handler := h.HandleIndex() - assert.NotNil(t, handler) + t.Run("unauthenticated redirects to login", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + w := httptest.NewRecorder() + + handler := h.HandleIndex() + handler.ServeHTTP(w, req) + + assert.Equal(t, http.StatusSeeOther, w.Code) + assert.Equal(t, "/pages/login", w.Header().Get("Location")) + }) + + t.Run("authenticated redirects to sources", func(t *testing.T) { + // Create a request, set up an authenticated session, then test + req := httptest.NewRequest(http.MethodGet, "/", nil) + w := httptest.NewRecorder() + + // Get a session and mark it as authenticated + s, err := sess.Get(req) + assert.NoError(t, err) + sess.SetUser(s, "test-user-id", "testuser") + err = sess.Save(req, w, s) + assert.NoError(t, err) + + // Build a new request with the session cookie from the response + req2 := httptest.NewRequest(http.MethodGet, "/", nil) + for _, cookie := range w.Result().Cookies() { + req2.AddCookie(cookie) + } + w2 := httptest.NewRecorder() + + handler := h.HandleIndex() + handler.ServeHTTP(w2, req2) + + assert.Equal(t, http.StatusSeeOther, w2.Code) + assert.Equal(t, "/sources", w2.Header().Get("Location")) + }) } func TestRenderTemplate(t *testing.T) { @@ -96,37 +127,3 @@ func TestRenderTemplate(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, w.Code) }) } - -func TestFormatUptime(t *testing.T) { - tests := []struct { - name string - duration string - expected string - }{ - { - name: "minutes only", - duration: "45m", - expected: "45m", - }, - { - name: "hours and minutes", - duration: "2h30m", - expected: "2h 30m", - }, - { - name: "days, hours and minutes", - duration: "25h45m", - expected: "1d 1h 45m", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d, err := time.ParseDuration(tt.duration) - require.NoError(t, err) - - result := formatUptime(d) - assert.Equal(t, tt.expected, result) - }) - } -} diff --git a/internal/handlers/index.go b/internal/handlers/index.go index 2dec0d3..5984f6d 100644 --- a/internal/handlers/index.go +++ b/internal/handlers/index.go @@ -1,49 +1,20 @@ package handlers import ( - "fmt" "net/http" - "time" - - "sneak.berlin/go/webhooker/internal/database" ) +// HandleIndex returns a handler for the root path that redirects based +// on authentication state: authenticated users go to /sources (the +// dashboard), unauthenticated users go to the login page. func (s *Handlers) HandleIndex() http.HandlerFunc { - // Calculate server start time - startTime := time.Now() - - return func(w http.ResponseWriter, req *http.Request) { - // Calculate uptime - uptime := time.Since(startTime) - uptimeStr := formatUptime(uptime) - - // Get user count from database - var userCount int64 - s.db.DB().Model(&database.User{}).Count(&userCount) - - // Prepare template data - data := map[string]interface{}{ - "Version": s.params.Globals.Version, - "Uptime": uptimeStr, - "UserCount": userCount, + return func(w http.ResponseWriter, r *http.Request) { + sess, err := s.session.Get(r) + if err == nil && s.session.IsAuthenticated(sess) { + http.Redirect(w, r, "/sources", http.StatusSeeOther) + return } - // Render the template - s.renderTemplate(w, req, "index.html", data) + http.Redirect(w, r, "/pages/login", http.StatusSeeOther) } } - -// formatUptime formats a duration into a human-readable string -func formatUptime(d time.Duration) string { - days := int(d.Hours()) / 24 - hours := int(d.Hours()) % 24 - minutes := int(d.Minutes()) % 60 - - if days > 0 { - return fmt.Sprintf("%dd %dh %dm", days, hours, minutes) - } - if hours > 0 { - return fmt.Sprintf("%dh %dm", hours, minutes) - } - return fmt.Sprintf("%dm", minutes) -}