feat: redirect root path based on auth state
All checks were successful
check / check (push) Successful in 1m53s
All checks were successful
check / check (push) Successful in 1m53s
/ 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.
This commit is contained in:
@@ -652,7 +652,7 @@ against a misbehaving sender).
|
|||||||
|
|
||||||
| Method | Path | Description |
|
| 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` | `/.well-known/healthcheck` | Health check (JSON: status, uptime, version) |
|
||||||
| `GET` | `/s/*` | Static file serving (embedded CSS, JS) |
|
| `GET` | `/s/*` | Static file serving (embedded CSS, JS) |
|
||||||
| `ANY` | `/webhook/{uuid}` | Webhook receiver endpoint (accepts all methods) |
|
| `ANY` | `/webhook/{uuid}` | Webhook receiver endpoint (accepts all methods) |
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
|
|||||||
|
|
||||||
// Parse all page templates once at startup
|
// Parse all page templates once at startup
|
||||||
s.templates = map[string]*template.Template{
|
s.templates = map[string]*template.Template{
|
||||||
"index.html": parsePageTemplate("index.html"),
|
|
||||||
"login.html": parsePageTemplate("login.html"),
|
"login.html": parsePageTemplate("login.html"),
|
||||||
"profile.html": parsePageTemplate("profile.html"),
|
"profile.html": parsePageTemplate("profile.html"),
|
||||||
"sources_list.html": parsePageTemplate("sources_list.html"),
|
"sources_list.html": parsePageTemplate("sources_list.html"),
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"go.uber.org/fx/fxtest"
|
"go.uber.org/fx/fxtest"
|
||||||
"sneak.berlin/go/webhooker/internal/config"
|
"sneak.berlin/go/webhooker/internal/config"
|
||||||
@@ -26,6 +24,7 @@ func (n *noopNotifier) Notify([]delivery.DeliveryTask) {}
|
|||||||
|
|
||||||
func TestHandleIndex(t *testing.T) {
|
func TestHandleIndex(t *testing.T) {
|
||||||
var h *Handlers
|
var h *Handlers
|
||||||
|
var sess *session.Session
|
||||||
|
|
||||||
app := fxtest.New(
|
app := fxtest.New(
|
||||||
t,
|
t,
|
||||||
@@ -44,15 +43,47 @@ func TestHandleIndex(t *testing.T) {
|
|||||||
func() delivery.Notifier { return &noopNotifier{} },
|
func() delivery.Notifier { return &noopNotifier{} },
|
||||||
New,
|
New,
|
||||||
),
|
),
|
||||||
fx.Populate(&h),
|
fx.Populate(&h, &sess),
|
||||||
)
|
)
|
||||||
app.RequireStart()
|
app.RequireStart()
|
||||||
defer app.RequireStop()
|
defer app.RequireStop()
|
||||||
|
|
||||||
// Since we can't test actual template rendering without templates,
|
t.Run("unauthenticated redirects to login", func(t *testing.T) {
|
||||||
// let's test that the handler is created and doesn't panic
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
handler := h.HandleIndex()
|
w := httptest.NewRecorder()
|
||||||
assert.NotNil(t, handler)
|
|
||||||
|
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) {
|
func TestRenderTemplate(t *testing.T) {
|
||||||
@@ -96,37 +127,3 @@ func TestRenderTemplate(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,49 +1,20 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"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 {
|
func (s *Handlers) HandleIndex() http.HandlerFunc {
|
||||||
// Calculate server start time
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
startTime := time.Now()
|
sess, err := s.session.Get(r)
|
||||||
|
if err == nil && s.session.IsAuthenticated(sess) {
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
http.Redirect(w, r, "/sources", http.StatusSeeOther)
|
||||||
// Calculate uptime
|
return
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the template
|
http.Redirect(w, r, "/pages/login", http.StatusSeeOther)
|
||||||
s.renderTemplate(w, req, "index.html", data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user