Further auth and authz configuration
This commit is contained in:
+22
-11
@@ -27,21 +27,21 @@ func NewKeyManagementHandler(keyStore *auth.HybridKeyStore, logger *logging.Logg
|
||||
|
||||
// CreateKeyRequest is the request body for creating a new API key
|
||||
type CreateKeyRequest struct {
|
||||
ClientID string `json:"client_id"`
|
||||
Roles []string `json:"roles"`
|
||||
RateLimit int `json:"rate_limit,omitempty"`
|
||||
ExpiresIn *time.Duration `json:"expires_in,omitempty"`
|
||||
ClientID string `json:"client_id"`
|
||||
Roles []string `json:"roles"`
|
||||
RateLimit int `json:"rate_limit,omitempty"`
|
||||
ExpiresIn string `json:"expires_in,omitempty"` // Duration string like "8760h", "30d", "1h", etc.
|
||||
}
|
||||
|
||||
// CreateKeyResponse is the response body when creating an API key
|
||||
type CreateKeyResponse struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
Roles []string `json:"roles"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
Roles []string `json:"roles"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
RateLimit int `json:"rate_limit"`
|
||||
RateLimit int `json:"rate_limit"`
|
||||
}
|
||||
|
||||
// ListKeysResponse is the response body for listing API keys
|
||||
@@ -103,8 +103,19 @@ func (h *KeyManagementHandler) CreateKey(w http.ResponseWriter, r *http.Request)
|
||||
req.RateLimit = 100
|
||||
}
|
||||
|
||||
// Parse expires_in duration string if provided
|
||||
var expiresInDuration *time.Duration
|
||||
if req.ExpiresIn != "" {
|
||||
duration, err := time.ParseDuration(req.ExpiresIn)
|
||||
if err != nil {
|
||||
h.respondError(w, http.StatusBadRequest, "Invalid expires_in format", fmt.Sprintf("expected duration format like '8760h' or '30d': %v", err))
|
||||
return
|
||||
}
|
||||
expiresInDuration = &duration
|
||||
}
|
||||
|
||||
// Create the key
|
||||
apiKey, err := h.keyStore.CreateKey(ctx, req.ClientID, req.Roles, req.RateLimit, req.ExpiresIn, authCtx.ClientID)
|
||||
apiKey, err := h.keyStore.CreateKey(ctx, req.ClientID, req.Roles, req.RateLimit, expiresInDuration, authCtx.ClientID)
|
||||
if err != nil {
|
||||
h.logger.Errorf("Failed to create API key: %v", err)
|
||||
h.respondError(w, http.StatusInternalServerError, "Failed to create API key", err.Error())
|
||||
|
||||
+18
-61
@@ -2,8 +2,6 @@ package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/igodwin/notifier/internal/auth"
|
||||
@@ -45,11 +43,16 @@ func DefaultCORSConfig() *CORSConfig {
|
||||
|
||||
// NewRouter creates a new HTTP router with all routes configured
|
||||
func NewRouter(service domain.NotificationService, logger *logging.Logger) *mux.Router {
|
||||
return NewRouterWithAuth(service, logger, nil, DefaultCORSConfig())
|
||||
return NewRouterWithAuth(service, logger, nil)
|
||||
}
|
||||
|
||||
// NewRouterWithAuth creates a new HTTP router with optional authentication and CORS configuration
|
||||
func NewRouterWithAuth(service domain.NotificationService, logger *logging.Logger, authStore *auth.APIKeyStore, corsConfig *CORSConfig) *mux.Router {
|
||||
func NewRouterWithAuth(service domain.NotificationService, logger *logging.Logger, authStore *auth.APIKeyStore) *mux.Router {
|
||||
return NewRouterWithAuthAndKeyStore(service, logger, authStore, nil)
|
||||
}
|
||||
|
||||
// NewRouterWithAuthAndKeyStore creates a new HTTP router with authentication and key management
|
||||
func NewRouterWithAuthAndKeyStore(service domain.NotificationService, logger *logging.Logger, authStore *auth.APIKeyStore, keyStore *auth.HybridKeyStore) *mux.Router {
|
||||
handler := NewHandler(service, logger)
|
||||
router := mux.NewRouter()
|
||||
|
||||
@@ -76,14 +79,21 @@ func NewRouterWithAuth(service domain.NotificationService, logger *logging.Logge
|
||||
// Notifiers route
|
||||
v1.HandleFunc("/notifiers", handler.GetNotifiers).Methods(http.MethodGet)
|
||||
|
||||
// Key management routes (requires auth and keystore)
|
||||
if authStore != nil && keyStore != nil {
|
||||
keyHandler := NewKeyManagementHandler(keyStore, logger)
|
||||
v1.HandleFunc("/admin/keys", keyHandler.CreateKey).Methods(http.MethodPost)
|
||||
v1.HandleFunc("/admin/keys", keyHandler.ListKeys).Methods(http.MethodGet)
|
||||
v1.HandleFunc("/admin/keys/{key}", keyHandler.RevokeKey).Methods(http.MethodDelete)
|
||||
v1.HandleFunc("/admin/keys/{key}/rotate", keyHandler.RotateKey).Methods(http.MethodPost)
|
||||
v1.HandleFunc("/admin/keys/{key}/audit", keyHandler.GetAuditLog).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
// Health check route (no auth required)
|
||||
router.HandleFunc("/health", handler.HealthCheck).Methods(http.MethodGet)
|
||||
|
||||
// Middleware - CORS must be applied before auth to handle preflight requests
|
||||
// Middleware - logging and CORS
|
||||
router.Use(loggingMiddleware)
|
||||
if corsConfig != nil {
|
||||
router.Use(newCORSMiddleware(corsConfig))
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -95,56 +105,3 @@ func loggingMiddleware(next http.Handler) http.Handler {
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// newCORSMiddleware creates a CORS middleware with origin whitelist validation
|
||||
func newCORSMiddleware(config *CORSConfig) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
// Check if the origin is in the allowed list
|
||||
allowed := false
|
||||
for _, allowedOrigin := range config.AllowedOrigins {
|
||||
if origin == allowedOrigin {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Only set CORS headers if the origin is allowed
|
||||
if allowed {
|
||||
// Set the exact origin (never use wildcard)
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
|
||||
// Set allowed methods
|
||||
if len(config.AllowedMethods) > 0 {
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(config.AllowedMethods, ", "))
|
||||
}
|
||||
|
||||
// Set allowed headers
|
||||
if len(config.AllowedHeaders) > 0 {
|
||||
w.Header().Set("Access-Control-Allow-Headers", strings.Join(config.AllowedHeaders, ", "))
|
||||
}
|
||||
|
||||
// Set credentials header if enabled
|
||||
if config.AllowCredentials {
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
// Set max age for preflight caching
|
||||
if config.MaxAge > 0 {
|
||||
w.Header().Set("Access-Control-Max-Age", strconv.FormatInt(int64(config.MaxAge), 10))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle preflight OPTIONS requests
|
||||
if r.Method == http.MethodOptions {
|
||||
// Return 200 OK for preflight requests
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user