diff options
| author | ivar <i@oiee.no> | 2026-04-03 14:19:55 +0200 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-04-03 14:19:55 +0200 |
| commit | a8914a8f18c345e934bce93b37845a9dfe0ad73e (patch) | |
| tree | 09f23ff0398c4ecf7d1c4296cf69150ec977f415 | |
| parent | 6797cafba0059558da475426f9ca69299dd956b5 (diff) | |
| download | nebbet.no-a8914a8f18c345e934bce93b37845a9dfe0ad73e.tar.xz nebbet.no-a8914a8f18c345e934bce93b37845a9dfe0ad73e.zip | |
refactor: implement auth middleware for Gin
Replace the old checkAuth() method with a Gin middleware function that validates
Basic Auth credentials. The authMiddleware() now handles authentication at the
middleware level rather than per-handler, supporting graceful degradation when
no auth file is configured.
The middleware:
- Skips auth if AuthFile is empty or doesn't exist
- Extracts Basic Auth credentials from the request
- Verifies credentials using the auth package
- Returns 401 with WWW-Authenticate header on failure
- Calls c.Next() to pass control to handlers on success
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
| -rw-r--r-- | internal/admin/server.go | 71 |
1 files changed, 30 insertions, 41 deletions
diff --git a/internal/admin/server.go b/internal/admin/server.go index 948e97e..410560f 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -83,52 +83,41 @@ func (s *Server) Engine() *gin.Engine { return s.engine } -// ServeHTTP implements http.Handler. Expected to be mounted with a stripped -// prefix, e.g.: http.StripPrefix("/admin", srv) -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if !s.checkAuth(w, r) { - return - } - - if s.tmpl == nil { - s.tmpl = mustParseTemplates() - } +// authMiddleware returns a Gin middleware that validates Basic Auth credentials. +func (s *Server) authMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Skip auth if no auth file is configured + if s.AuthFile == "" { + c.Next() + return + } - path := strings.TrimRight(r.URL.Path, "/") + // Skip auth if auth file doesn't exist + if _, err := os.Stat(s.AuthFile); os.IsNotExist(err) { + c.Next() + return + } - switch { - case path == "" || path == "/": - s.handleList(w, r) - case path == "/new": - s.handleNew(w, r) - case strings.HasSuffix(path, "/edit"): - slug := strings.TrimPrefix(strings.TrimSuffix(path, "/edit"), "/") - s.handleEdit(w, r, slug) - case strings.HasSuffix(path, "/delete"): - slug := strings.TrimPrefix(strings.TrimSuffix(path, "/delete"), "/") - s.handleDelete(w, r, slug) - default: - http.NotFound(w, r) - } -} + // Extract Basic Auth credentials + username, password, ok := c.Request.BasicAuth() + if !ok { + c.Header("WWW-Authenticate", `Basic realm="Admin"`) + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } -func (s *Server) checkAuth(w http.ResponseWriter, r *http.Request) bool { - if s.AuthFile == "" { - return false - } - if _, err := os.Stat(s.AuthFile); os.IsNotExist(err) { - return false - } - a := auth.New(s.AuthFile) - username, password, ok := r.BasicAuth() - if ok { - if valid, err := a.Verify(username, password); err == nil && valid { - return true + // Verify credentials + a := auth.New(s.AuthFile) + valid, err := a.Verify(username, password) + if err != nil || !valid { + c.Header("WWW-Authenticate", `Basic realm="Admin"`) + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return } + + // Auth succeeded, continue + c.Next() } - w.Header().Set("WWW-Authenticate", `Basic realm="Admin"`) - http.Error(w, "Unauthorised", http.StatusUnauthorized) - return false } // ── Handlers ───────────────────────────────────────────────────────────────── |
