Add bearer token API authentication
- Auto-generate random 32-char bearer token on first startup, stored in secrets.yaml as api.bearerToken - BearerAuthMiddleware checks Authorization: Bearer <token> on all /api/ endpoints except /health, /health/reconcile, /events (SSE), and non-API paths (frontend static files) - Development mode (WILD_CENTRAL_ENV=development) skips auth entirely - Web app ApiClient: add setToken/clearToken/hasToken methods, persist token in localStorage, automatically include Authorization header on all API requests - Token can be found in secrets.yaml for CLI/automation use
This commit is contained in:
@@ -37,6 +37,43 @@ func (w *statusResponseWriter) Flush() {
|
||||
}
|
||||
}
|
||||
|
||||
// BearerAuthMiddleware returns middleware that requires a valid Bearer token
|
||||
// on all API endpoints except health checks and the SSE event stream.
|
||||
func BearerAuthMiddleware(token string) mux.MiddlewareFunc {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
|
||||
// Public endpoints — no auth required
|
||||
if path == "/api/v1/health" || path == "/api/v1/health/reconcile" ||
|
||||
strings.HasSuffix(path, "/events") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Non-API paths (frontend static files) — no auth required
|
||||
if !strings.HasPrefix(path, "/api/") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
http.Error(w, `{"error":"authentication required"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
const prefix = "Bearer "
|
||||
if !strings.HasPrefix(auth, prefix) || auth[len(prefix):] != token {
|
||||
http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RequestLoggingMiddleware logs method, path, status, and duration for each request.
|
||||
// Long-lived connections (SSE, WebSocket) are excluded.
|
||||
func RequestLoggingMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
Reference in New Issue
Block a user