First commit

This commit is contained in:
stef
2025-12-10 11:03:29 +01:00
commit db87e3be3d
18 changed files with 3211 additions and 0 deletions

380
internal/api/handlers.go Normal file
View File

@@ -0,0 +1,380 @@
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)
}

67
internal/api/routes.go Normal file
View File

@@ -0,0 +1,67 @@
package api
import (
"log"
"pki-manager/config"
"pki-manager/internal/repository"
"pki-manager/internal/services"
"github.com/gin-gonic/gin"
)
func SetupRoutes(router *gin.Engine, repo repository.Repository, jwtSecret string) {
cfg := config.LoadConfig()
cryptoService := services.NewCryptoService(cfg.CertsPath)
handlers := NewHandlers(repo, cryptoService)
// Add logging middleware
router.Use(func(c *gin.Context) {
log.Printf("[API] %s %s", c.Request.Method, c.Request.URL.Path)
c.Next()
})
// Load HTML templates
router.LoadHTMLGlob("internal/web/templates/*")
router.Static("/static", "internal/web/static")
// Web interface
router.GET("/", handlers.ServeWebInterface)
// API routes
api := router.Group("/api/v1")
{
// CA routes
ca := api.Group("/cas")
{
ca.POST("/", handlers.CreateCA)
ca.GET("/", handlers.GetAllCAs)
ca.GET("/:id", handlers.GetCA)
ca.PUT("/:id", handlers.UpdateCA)
ca.DELETE("/:id", handlers.DeleteCA)
ca.GET("/:id/download/cert", handlers.DownloadCACertificate)
}
// SubCA routes
subca := api.Group("/subcas")
{
subca.POST("/", handlers.CreateSubCA)
subca.GET("/", handlers.GetAllSubCAs)
subca.GET("/:id", handlers.GetSubCA)
subca.PUT("/:id", handlers.UpdateSubCA)
subca.DELETE("/:id", handlers.DeleteSubCA)
subca.GET("/:id/download/cert", handlers.DownloadSubCACertificate)
}
// Certificate routes
cert := api.Group("/certificates")
{
cert.POST("/", handlers.CreateCertificate)
cert.GET("/", handlers.GetAllCertificates)
cert.GET("/:id", handlers.GetCertificate)
cert.DELETE("/:id", handlers.DeleteCertificate)
cert.POST("/:id/revoke", handlers.RevokeCertificate)
cert.GET("/:id/download/cert", handlers.DownloadCertificate)
cert.GET("/:id/download/key", handlers.DownloadPrivateKey)
}
}
}