package handler import ( "github.com/gofiber/fiber/v2" "github.com/gosec/gsc-ops-api/internal/middleware" "github.com/gosec/gsc-ops-api/internal/service" "github.com/gosec/gsc-ops-api/pkg/types" ) // APIKeyHandler exposes dynamic API-key management. These routes sit behind the // same API-key middleware as the rest of /api/v1, so an existing valid key // bootstraps the first dynamic ones. type APIKeyHandler struct { svc *service.APIKeyService } // NewAPIKeyHandler creates a new API-key handler. func NewAPIKeyHandler(svc *service.APIKeyService) *APIKeyHandler { return &APIKeyHandler{svc: svc} } // Create handles POST /api/v1/admin/api-keys — mints a key and returns the // plaintext exactly once. func (h *APIKeyHandler) Create(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.APIKeyCreateRequest if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.Name == "" { apiErr := types.NewValidation("name is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := types.ValidateScopes(req.Scopes); err != nil { apiErr := types.NewValidation(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } createdBy := "" if claims := middleware.GetJWTClaims(c); claims != nil { createdBy = claims.Subject } info, err := h.svc.Generate(c.Context(), req.Name, req.Scopes, createdBy) if err != nil { apiErr := types.NewConflict(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(info, reqID)) } // List handles GET /api/v1/admin/api-keys. func (h *APIKeyHandler) List(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) keys, err := h.svc.List(c.Context()) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(keys, int64(len(keys)), len(keys), 0, reqID)) } // Revoke handles DELETE /api/v1/admin/api-keys/:id. func (h *APIKeyHandler) Revoke(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id := c.Params("id") if id == "" { apiErr := types.NewBadRequest("id is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.Revoke(c.Context(), id); err != nil { apiErr := types.NewNotFound(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.SendStatus(fiber.StatusNoContent) }