package api import ( "encoding/json" "net/http" "time" "dance-lessons-coach/pkg/user" "github.com/go-chi/chi/v5" ) // AdminHandler handles admin-related HTTP requests type AdminHandler struct { authService user.AuthService } // NewAdminHandler creates a new admin handler func NewAdminHandler(authService user.AuthService) *AdminHandler { return &AdminHandler{ authService: authService, } } // RegisterRoutes registers admin routes func (h *AdminHandler) RegisterRoutes(router chi.Router) { router.Route("/jwt", func(r chi.Router) { r.Post("/secrets", h.handleAddJWTSecret) r.Post("/secrets/rotate", h.handleRotateJWTSecret) }) } // AddJWTSecretRequest represents a request to add a new JWT secret type AddJWTSecretRequest struct { Secret string `json:"secret" validate:"required,min=16"` IsPrimary bool `json:"is_primary"` ExpiresIn int64 `json:"expires_in"` // Expiration time in hours } // handleAddJWTSecret godoc // // @Summary Add JWT secret // @Description Add a new JWT secret for rotation purposes // @Tags API/v1/Admin // @Accept json // @Produce json // @Param request body AddJWTSecretRequest true "JWT secret details" // @Success 200 {object} map[string]string "Secret added successfully" // @Failure 400 {object} map[string]string "Invalid request" // @Failure 401 {object} map[string]string "Unauthorized" // @Failure 500 {object} map[string]string "Server error" // @Router /v1/admin/jwt/secrets [post] func (h *AdminHandler) handleAddJWTSecret(w http.ResponseWriter, r *http.Request) { // Decode request body into a map to handle flexible boolean parsing var body map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Extract and validate fields secret, ok := body["secret"].(string) if !ok || secret == "" { http.Error(w, `{"error":"invalid_request","message":"secret is required and must be a string"}`, http.StatusBadRequest) return } // Handle is_primary as either bool or string isPrimary := false // default if val, exists := body["is_primary"]; exists { switch v := val.(type) { case bool: isPrimary = v case string: isPrimary = v == "true" default: http.Error(w, `{"error":"invalid_request","message":"is_primary must be a boolean or string"}`, http.StatusBadRequest) return } } // Handle expires_in as either int64 or float64 (JSON numbers) expiresInHours := int64(0) if val, exists := body["expires_in"]; exists { switch v := val.(type) { case int64: expiresInHours = v case float64: expiresInHours = int64(v) default: http.Error(w, `{"error":"invalid_request","message":"expires_in must be a number"}`, http.StatusBadRequest) return } } // Convert expires_in from hours to time.Duration expiresIn := time.Duration(expiresInHours) * time.Hour if expiresIn <= 0 { // If expires_in is 0 or not provided, set to no expiration for secondary secrets // For primary secrets, use a reasonable default if isPrimary { expiresIn = 24 * 365 * time.Hour // 1 year for primary secrets } else { expiresIn = 0 // No expiration for secondary secrets } } // Add the secret to the manager h.authService.AddJWTSecret(secret, isPrimary, expiresIn) // Return success w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "JWT secret added successfully"}) } // RotateJWTSecretRequest represents a request to rotate JWT secrets type RotateJWTSecretRequest struct { NewSecret string `json:"new_secret" validate:"required,min=16"` } // handleRotateJWTSecret godoc // // @Summary Rotate JWT secret // @Description Rotate to a new primary JWT secret // @Tags API/v1/Admin // @Accept json // @Produce json // @Param request body RotateJWTSecretRequest true "New JWT secret" // @Success 200 {object} map[string]string "Secret rotated successfully" // @Failure 400 {object} map[string]string "Invalid request" // @Failure 401 {object} map[string]string "Unauthorized" // @Failure 500 {object} map[string]string "Server error" // @Router /v1/admin/jwt/secrets/rotate [post] func (h *AdminHandler) handleRotateJWTSecret(w http.ResponseWriter, r *http.Request) { var req RotateJWTSecretRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Rotate to the new secret h.authService.RotateJWTSecret(req.NewSecret) // Return success w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "JWT secret rotated successfully"}) }