| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- 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,
- })
- }
|