package api import ( "net/http" "pki-manager/internal/models" "pki-manager/internal/repository" "pki-manager/internal/services" "github.com/gin-gonic/gin" ) type Handlers struct { repo repository.Repository cryptoService *services.CryptoService } func NewHandlers(repo repository.Repository, cryptoService *services.CryptoService) *Handlers { return &Handlers{ repo: repo, cryptoService: cryptoService, } } // CA Handlers func (h *Handlers) CreateCA(c *gin.Context) { var req models.CreateCARequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ca, err := h.cryptoService.GenerateRootCA(req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := h.repo.CreateCA(c.Request.Context(), ca); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, ca) } func (h *Handlers) GetCA(c *gin.Context) { id := c.Param("id") ca, err := h.repo.GetCA(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "CA not found"}) return } // Don't expose private key in GET requests ca.PrivateKey = "" c.JSON(http.StatusOK, ca) } // GetAllCAs - Retourne toujours un tableau, même vide func (h *Handlers) GetAllCAs(c *gin.Context) { cas, err := h.repo.GetAllCAs(c.Request.Context()) if err != nil { // IMPORTANT: Retourner un tableau vide, pas une erreur c.JSON(http.StatusOK, []interface{}{}) return } // Remove private keys from response for _, ca := range cas { ca.PrivateKey = "" } // S'assurer qu'on retourne toujours un tableau if cas == nil { c.JSON(http.StatusOK, []interface{}{}) } else { c.JSON(http.StatusOK, cas) } } func (h *Handlers) UpdateCA(c *gin.Context) { id := c.Param("id") var req models.UpdateCARequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := make(map[string]interface{}) if req.Name != "" { updates["name"] = req.Name } if req.Organization != "" { updates["organization"] = req.Organization } if req.Email != "" { updates["email"] = req.Email } if err := h.repo.UpdateCA(c.Request.Context(), id, updates); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "CA updated successfully"}) } func (h *Handlers) DeleteCA(c *gin.Context) { id := c.Param("id") if err := h.repo.DeleteCA(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "CA deleted successfully"}) } // SubCA Handlers func (h *Handlers) CreateSubCA(c *gin.Context) { var req models.CreateSubCARequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Get parent CA parentCA, err := h.repo.GetCA(c.Request.Context(), req.ParentCAID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Parent CA not found"}) return } subca, err := h.cryptoService.GenerateSubCA(req, parentCA) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := h.repo.CreateSubCA(c.Request.Context(), subca); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, subca) } func (h *Handlers) GetSubCA(c *gin.Context) { id := c.Param("id") subca, err := h.repo.GetSubCA(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "SubCA not found"}) return } subca.PrivateKey = "" c.JSON(http.StatusOK, subca) } // GetAllSubCAs - Retourne toujours un tableau, même vide func (h *Handlers) GetAllSubCAs(c *gin.Context) { subcas, err := h.repo.GetAllSubCAs(c.Request.Context()) if err != nil { // IMPORTANT: Retourner un tableau vide, pas une erreur c.JSON(http.StatusOK, []interface{}{}) return } // Remove private keys from response for _, subca := range subcas { subca.PrivateKey = "" } // S'assurer qu'on retourne toujours un tableau if subcas == nil { c.JSON(http.StatusOK, []interface{}{}) } else { c.JSON(http.StatusOK, subcas) } } func (h *Handlers) UpdateSubCA(c *gin.Context) { id := c.Param("id") var req models.UpdateSubCARequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := make(map[string]interface{}) if req.Name != "" { updates["name"] = req.Name } if req.Organization != "" { updates["organization"] = req.Organization } if req.Email != "" { updates["email"] = req.Email } if err := h.repo.UpdateSubCA(c.Request.Context(), id, updates); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "SubCA updated successfully"}) } func (h *Handlers) DeleteSubCA(c *gin.Context) { id := c.Param("id") if err := h.repo.DeleteSubCA(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "SubCA deleted successfully"}) } // Certificate Handlers func (h *Handlers) CreateCertificate(c *gin.Context) { var req models.CreateCertificateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Try to get issuer as CA first issuer, err := h.repo.GetCA(c.Request.Context(), req.IssuerCAID) if err != nil { // If not found as CA, try as SubCA issuer, err := h.repo.GetSubCA(c.Request.Context(), req.IssuerCAID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Issuer not found"}) return } cert, err := h.cryptoService.GenerateCertificate(req, issuer) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := h.repo.CreateCertificate(c.Request.Context(), cert); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, cert) return } cert, err := h.cryptoService.GenerateCertificate(req, issuer) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := h.repo.CreateCertificate(c.Request.Context(), cert); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, cert) } func (h *Handlers) GetCertificate(c *gin.Context) { id := c.Param("id") cert, err := h.repo.GetCertificate(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Certificate not found"}) return } cert.PrivateKey = "" c.JSON(http.StatusOK, cert) } // GetAllCertificates - Retourne toujours un tableau, même vide func (h *Handlers) GetAllCertificates(c *gin.Context) { certs, err := h.repo.GetAllCertificates(c.Request.Context()) if err != nil { // IMPORTANT: Retourner un tableau vide, pas une erreur c.JSON(http.StatusOK, []interface{}{}) return } // Remove private keys from response for _, cert := range certs { cert.PrivateKey = "" } // S'assurer qu'on retourne toujours un tableau if certs == nil { c.JSON(http.StatusOK, []interface{}{}) } else { c.JSON(http.StatusOK, certs) } } func (h *Handlers) DeleteCertificate(c *gin.Context) { id := c.Param("id") if err := h.repo.DeleteCertificate(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Certificate deleted successfully"}) } func (h *Handlers) RevokeCertificate(c *gin.Context) { id := c.Param("id") var req models.RevokeCertificateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.repo.RevokeCertificate(c.Request.Context(), id, req.Reason); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Certificate revoked successfully"}) } // Download handlers for CA and SubCA func (h *Handlers) DownloadCACertificate(c *gin.Context) { id := c.Param("id") ca, err := h.repo.GetCA(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "CA not found"}) return } c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+ca.CommonName+".crt") c.String(http.StatusOK, ca.Certificate) } func (h *Handlers) DownloadSubCACertificate(c *gin.Context) { id := c.Param("id") subca, err := h.repo.GetSubCA(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "SubCA not found"}) return } c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+subca.CommonName+".crt") c.String(http.StatusOK, subca.Certificate) } // Download handlers func (h *Handlers) DownloadCertificate(c *gin.Context) { id := c.Param("id") cert, err := h.repo.GetCertificate(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Certificate not found"}) return } c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+cert.CommonName+".crt") c.String(http.StatusOK, cert.Certificate) } func (h *Handlers) DownloadPrivateKey(c *gin.Context) { id := c.Param("id") cert, err := h.repo.GetCertificate(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Certificate not found"}) return } c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+cert.CommonName+".key") c.String(http.StatusOK, cert.PrivateKey) } // Web Interface func (h *Handlers) ServeWebInterface(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) }