From 4a0af136d55a13ba29cf4dd0b8d8cddea8bde6a7 Mon Sep 17 00:00:00 2001 From: Matthew Knight Date: Tue, 17 Feb 2026 15:53:31 -0800 Subject: [PATCH] Add CSRF protection to admin panel Fixes #14 Co-Authored-By: Claude Opus 4.6 --- cmd/server/main.go | 1 + internal/handlers/admin/routes.go | 53 +++++++++++-------- web/templates/pages/admin/repos/edit.html | 1 + web/templates/pages/admin/repos/new.html | 1 + web/templates/pages/admin/tickets/detail.html | 1 + web/templates/pages/admin/users/detail.html | 1 + web/templates/pages/admin/users/new.html | 1 + web/templates/pages/admin/users/pending.html | 2 + 8 files changed, 40 insertions(+), 21 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 1f264ed..06685cb 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -76,6 +76,7 @@ func main() { DB: db, Renderer: renderer, Auth: authService, + SessionStore: sessionStore, EmailClient: emailClient, ForgejoClient: forgejoClient, Config: cfg, diff --git a/internal/handlers/admin/routes.go b/internal/handlers/admin/routes.go index 1785be9..0d8d0cd 100644 --- a/internal/handlers/admin/routes.go +++ b/internal/handlers/admin/routes.go @@ -1,6 +1,8 @@ package admin import ( + "strings" + "github.com/gin-gonic/gin" "github.com/mattnite/forgejo-tickets/internal/auth" "github.com/mattnite/forgejo-tickets/internal/config" @@ -15,6 +17,7 @@ type Dependencies struct { DB *gorm.DB Renderer *templates.Renderer Auth *auth.Service + SessionStore *auth.PGStore EmailClient *email.Client ForgejoClient *forgejo.Client Config *config.Config @@ -30,30 +33,38 @@ func NewRouter(deps Dependencies) *gin.Engine { tsAuth := &TailscaleAuth{allowedUsers: deps.Config.TailscaleAllowedUsers} r.Use(tsAuth.Middleware) - dashboardHandler := &DashboardHandler{deps: deps} - r.GET("/", dashboardHandler.Index) + csrfSecret := []byte(deps.Config.SessionSecret) + isSecure := strings.HasPrefix(deps.Config.BaseURL, "https") + csrfMiddleware := middleware.CSRF(csrfSecret, isSecure) - userHandler := &UserHandler{deps: deps} - r.GET("/users", userHandler.List) - r.GET("/users/pending", userHandler.PendingList) - r.GET("/users/new", userHandler.NewForm) - r.GET("/users/:id", userHandler.Detail) - r.POST("/users", userHandler.Create) - r.POST("/users/:id/approve", userHandler.Approve) - r.POST("/users/:id/reject", userHandler.Reject) - r.POST("/users/:id/repos", userHandler.UpdateRepos) + csrf := r.Group("/") + csrf.Use(csrfMiddleware) + { + dashboardHandler := &DashboardHandler{deps: deps} + csrf.GET("/", dashboardHandler.Index) - ticketHandler := &TicketHandler{deps: deps} - r.GET("/tickets", ticketHandler.List) - r.GET("/tickets/:id", ticketHandler.Detail) - r.POST("/tickets/:id/status", ticketHandler.UpdateStatus) + userHandler := &UserHandler{deps: deps} + csrf.GET("/users", userHandler.List) + csrf.GET("/users/pending", userHandler.PendingList) + csrf.GET("/users/new", userHandler.NewForm) + csrf.GET("/users/:id", userHandler.Detail) + csrf.POST("/users", userHandler.Create) + csrf.POST("/users/:id/approve", userHandler.Approve) + csrf.POST("/users/:id/reject", userHandler.Reject) + csrf.POST("/users/:id/repos", userHandler.UpdateRepos) - repoHandler := &RepoHandler{deps: deps} - r.GET("/repos", repoHandler.List) - r.GET("/repos/new", repoHandler.NewForm) - r.POST("/repos", repoHandler.Create) - r.GET("/repos/:id/edit", repoHandler.EditForm) - r.POST("/repos/:id", repoHandler.Update) + ticketHandler := &TicketHandler{deps: deps} + csrf.GET("/tickets", ticketHandler.List) + csrf.GET("/tickets/:id", ticketHandler.Detail) + csrf.POST("/tickets/:id/status", ticketHandler.UpdateStatus) + + repoHandler := &RepoHandler{deps: deps} + csrf.GET("/repos", repoHandler.List) + csrf.GET("/repos/new", repoHandler.NewForm) + csrf.POST("/repos", repoHandler.Create) + csrf.GET("/repos/:id/edit", repoHandler.EditForm) + csrf.POST("/repos/:id", repoHandler.Update) + } return r } diff --git a/web/templates/pages/admin/repos/edit.html b/web/templates/pages/admin/repos/edit.html index c353ed3..ff508ef 100644 --- a/web/templates/pages/admin/repos/edit.html +++ b/web/templates/pages/admin/repos/edit.html @@ -64,6 +64,7 @@ {{end}}
+
+
+
{{range .AllRepos}}