312 sor
7.8 KiB
Go
312 sor
7.8 KiB
Go
package handlers
|
|
import (
|
|
"auto-ssl/config"
|
|
"auto-ssl/services"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type CertHandler struct {
|
|
Cfg *config.Config
|
|
}
|
|
|
|
func NewCertHandler(cfg *config.Config) *CertHandler {
|
|
return &CertHandler{Cfg: cfg}
|
|
}
|
|
|
|
type CreateCertRequest struct {
|
|
Domain string `json:"domain" binding:"required"`
|
|
Email string `json:"email" binding:"required"`
|
|
Provider string `json:"provider"`
|
|
ChallengeType string `json:"challenge_type"`
|
|
DNSProvider string `json:"dns_provider"`
|
|
DNSConfig string `json:"dns_config"`
|
|
AutoRenew *bool `json:"auto_renew"`
|
|
RenewDays *int `json:"renew_days"`
|
|
}
|
|
|
|
// ListCertificates returns all certificates
|
|
func (h *CertHandler) ListCertificates(c *gin.Context) {
|
|
certs := config.Store.GetAll()
|
|
c.JSON(http.StatusOK, certs)
|
|
}
|
|
|
|
// GetCertificate returns a single certificate
|
|
func (h *CertHandler) GetCertificate(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
cert := config.Store.GetByID(uint(id))
|
|
if cert == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, cert)
|
|
}
|
|
|
|
// CreateCertificate creates a new certificate entry and starts issuance
|
|
func (h *CertHandler) CreateCertificate(c *gin.Context) {
|
|
var req CreateCertRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if req.Provider == "" {
|
|
req.Provider = "letsencrypt"
|
|
}
|
|
if req.ChallengeType == "" {
|
|
req.ChallengeType = "http"
|
|
}
|
|
|
|
req.Domain = strings.TrimSpace(req.Domain)
|
|
|
|
autoRenew := true
|
|
renewDays := 30
|
|
if req.AutoRenew != nil {
|
|
autoRenew = *req.AutoRenew
|
|
}
|
|
if req.RenewDays != nil {
|
|
renewDays = *req.RenewDays
|
|
}
|
|
|
|
existing := config.Store.GetByDomain(req.Domain)
|
|
if existing != nil {
|
|
// Domain exists: only retry if it's in a retryable state
|
|
if existing.Status == "error" || existing.Status == "expired" || existing.Status == "pending" {
|
|
existing.Email = req.Email
|
|
existing.Provider = req.Provider
|
|
existing.ChallengeType = req.ChallengeType
|
|
existing.DNSProvider = req.DNSProvider
|
|
existing.DNSConfig = req.DNSConfig
|
|
existing.Status = "pending"
|
|
existing.ErrorMessage = ""
|
|
existing.AutoRenew = autoRenew
|
|
existing.RenewDays = renewDays
|
|
config.Store.Upsert(existing)
|
|
|
|
// Start issuance in background
|
|
go func() {
|
|
domain := existing.Domain
|
|
saved := config.Store.GetByDomain(domain)
|
|
if err := services.GetACMECertificate(saved, h.Cfg); err != nil {
|
|
saved.Status = "error"
|
|
saved.ErrorMessage = err.Error()
|
|
} else {
|
|
saved.Status = "active"
|
|
}
|
|
config.Store.Upsert(saved)
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"message": "certificate re-issuance started", "certificate": existing})
|
|
return
|
|
}
|
|
c.JSON(http.StatusConflict, gin.H{"error": "domain already exists with status: " + existing.Status})
|
|
return
|
|
}
|
|
|
|
// Create new certificate
|
|
cert := &config.Certificate{
|
|
Domain: req.Domain,
|
|
Email: req.Email,
|
|
Provider: req.Provider,
|
|
ChallengeType: req.ChallengeType,
|
|
DNSProvider: req.DNSProvider,
|
|
DNSConfig: req.DNSConfig,
|
|
Status: "pending",
|
|
AutoRenew: autoRenew,
|
|
RenewDays: renewDays,
|
|
}
|
|
|
|
if err := config.Store.Upsert(cert); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Start issuance in background
|
|
go func() {
|
|
domain := cert.Domain
|
|
saved := config.Store.GetByDomain(domain)
|
|
if err := services.GetACMECertificate(saved, h.Cfg); err != nil {
|
|
saved.Status = "error"
|
|
saved.ErrorMessage = err.Error()
|
|
} else {
|
|
saved.Status = "active"
|
|
}
|
|
config.Store.Upsert(saved)
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"message": "certificate issuance started", "certificate": cert})
|
|
}
|
|
|
|
// RenewCertificate manually renews a certificate
|
|
func (h *CertHandler) RenewCertificate(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
|
|
cert := config.Store.GetByID(uint(id))
|
|
if cert == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
|
|
return
|
|
}
|
|
|
|
cert.Status = "renewing"
|
|
config.Store.Upsert(cert)
|
|
|
|
go func() {
|
|
domain := cert.Domain
|
|
saved := config.Store.GetByDomain(domain)
|
|
if err := services.RenewCertificate(saved, h.Cfg); err != nil {
|
|
saved.Status = "error"
|
|
saved.ErrorMessage = err.Error()
|
|
} else {
|
|
saved.Status = "active"
|
|
}
|
|
config.Store.Upsert(saved)
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"message": "renewal started", "certificate": cert})
|
|
}
|
|
|
|
// DeleteCertificate deletes a certificate record and files
|
|
func (h *CertHandler) DeleteCertificate(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
|
|
if err := config.Store.Delete(uint(id)); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "certificate deleted"})
|
|
}
|
|
|
|
// UpdateCertificate updates certificate settings
|
|
func (h *CertHandler) UpdateCertificate(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
|
|
cert := config.Store.GetByID(uint(id))
|
|
if cert == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
|
|
return
|
|
}
|
|
|
|
var updates map[string]interface{}
|
|
if err := c.ShouldBindJSON(&updates); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if v, ok := updates["auto_renew"]; ok {
|
|
cert.AutoRenew, _ = v.(bool)
|
|
}
|
|
if v, ok := updates["renew_days"]; ok {
|
|
if f, ok := v.(float64); ok {
|
|
cert.RenewDays = int(f)
|
|
}
|
|
}
|
|
if v, ok := updates["dns_config"]; ok {
|
|
cert.DNSConfig, _ = v.(string)
|
|
}
|
|
|
|
config.Store.Upsert(cert)
|
|
c.JSON(http.StatusOK, cert)
|
|
}
|
|
|
|
// GetCertFiles returns the content of certificate files for download
|
|
func (h *CertHandler) GetCertFiles(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
|
|
cert := config.Store.GetByID(uint(id))
|
|
if cert == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
|
|
return
|
|
}
|
|
|
|
fullchain, privkey, chain := services.GetCertFileContents(cert.Domain, h.Cfg)
|
|
|
|
result := gin.H{
|
|
"domain": cert.Domain,
|
|
"fullchain": fullchain,
|
|
"privkey": privkey,
|
|
"chain": chain,
|
|
}
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// CheckRenewals checks all certificates and renews those about to expire
|
|
func (h *CertHandler) CheckRenewals(c *gin.Context) {
|
|
certs := config.Store.GetActiveWithAutoRenew()
|
|
|
|
renewed := []string{}
|
|
failed := []string{}
|
|
|
|
for _, cert := range certs {
|
|
if cert.ExpiresAt != nil && time.Until(*cert.ExpiresAt).Hours() < float64(cert.RenewDays*24) {
|
|
if err := services.RenewCertificate(cert, h.Cfg); err != nil {
|
|
cert.Status = "error"
|
|
cert.ErrorMessage = fmt.Sprintf("auto renew failed: %v", err)
|
|
failed = append(failed, cert.Domain)
|
|
} else {
|
|
cert.Status = "active"
|
|
renewed = append(renewed, cert.Domain)
|
|
}
|
|
config.Store.Upsert(cert)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "renewal check complete",
|
|
"renewed": renewed,
|
|
"failed": failed,
|
|
})
|
|
}
|
|
|
|
// Stats returns dashboard statistics
|
|
func (h *CertHandler) Stats(c *gin.Context) {
|
|
certs := config.Store.GetAll()
|
|
|
|
var total, active, expired, errors int
|
|
for _, cert := range certs {
|
|
total++
|
|
switch cert.Status {
|
|
case "active":
|
|
active++
|
|
case "expired":
|
|
expired++
|
|
case "error":
|
|
errors++
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"total": total,
|
|
"active": active,
|
|
"expired": expired,
|
|
"errors": errors,
|
|
})
|
|
}
|
|
|
|
|