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" ) // CertHandler handles certificate endpoints type CertHandler struct { svc *service.CertificateService } // NewCertHandler creates a new certificate handler func NewCertHandler(svc *service.CertificateService) *CertHandler { return &CertHandler{svc: svc} } // List handles GET /api/v1/certs func (h *CertHandler) List(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) search := c.Query("search") limit := c.QueryInt("limit", 50) certs, err := h.svc.ListCertificates(search, limit) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(certs, reqID)) } // Get handles GET /api/v1/certs/:serialNumber func (h *CertHandler) Get(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) serialNumber := c.Params("serialNumber") issuerDN := c.Query("issuerDn") if issuerDN == "" { apiErr := types.NewValidation("issuerDn query parameter is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } cert, err := h.svc.GetCertificate(serialNumber, issuerDN) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(cert, reqID)) } // Request handles POST /api/v1/certs/request func (h *CertHandler) Request(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.CertRequest 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.SubjectDN == "" || req.CAName == "" || req.CertProfileName == "" || req.EndEntityName == "" { apiErr := types.NewValidation("subjectDn, caName, certProfileName, and endEntityName are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } cert, err := h.svc.RequestCertificate(&req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(cert, reqID)) } // Renew handles POST /api/v1/certs/:serialNumber/renew func (h *CertHandler) Renew(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) serialNumber := c.Params("serialNumber") // For renewal, we re-request with the same parameters // The caller should provide the original cert's issuer DN issuerDN := c.Query("issuerDn") if issuerDN == "" { apiErr := types.NewValidation("issuerDn query parameter is required for renewal") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } // Get the existing cert to extract parameters existing, err := h.svc.GetCertificate(serialNumber, issuerDN) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } // Re-request with same subject cert, err := h.svc.RequestCertificate(&types.CertRequest{ SubjectDN: existing.SubjectDN, CAName: existing.CAName, CertProfileName: "SERVER", EndEntityName: existing.SubjectDN, }) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(cert, reqID)) } // Revoke handles POST /api/v1/certs/:serialNumber/revoke func (h *CertHandler) Revoke(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) serialNumber := c.Params("serialNumber") var req types.CertRevoke 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.IssuerDN == "" { apiErr := types.NewValidation("issuerDn is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.RevokeCertificate(serialNumber, &req); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{ "serialNumber": serialNumber, "revoked": true, }, reqID)) }