package web import ( "fmt" "net/http" "strconv" "github.com/owen/vibeproxy/internal/auth" "github.com/owen/vibeproxy/internal/certificates" "github.com/owen/vibeproxy/internal/database" "github.com/owen/vibeproxy/internal/logging" "github.com/owen/vibeproxy/internal/models" ) func (h *Web) handleCertificatesList(w http.ResponseWriter, r *http.Request) { certs, err := database.ListCertificates(h.db) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } render(w, "certificates", PageData{ Active: "certificates", Title: "Certificates", CSRFToken: h.csrf.Token(w), Flash: r.URL.Query().Get("flash"), FlashKind: r.URL.Query().Get("kind"), Data: certs, }) } func (h *Web) handleCertificateIssue(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } name := r.FormValue("name") domains := splitLines(r.FormValue("domains")) if name == "" || len(domains) == 0 { h.redirectCertError(w, r, "Name and at least one domain are required") return } settings := h.store.Current().Settings email := r.FormValue("email") if email == "" { email = settings.ACMEEmail } challengeType := models.ChallengeType(r.FormValue("challenge_type")) if challengeType == "" { challengeType = models.ChallengeHTTP01 } cert, err := h.certs.IssueLetsEncrypt(r.Context(), certificates.IssueLetsEncryptOptions{ Name: name, Domains: domains, Email: email, Staging: settings.ACMEStaging, ChallengeType: challengeType, DNSProvider: r.FormValue("dns_provider"), }) if err != nil { h.redirectCertError(w, r, "Issuance failed: "+err.Error()) return } logging.Audit(h.db, auth.ActorFromContext(r.Context()), "certificate_issued", cert.Name, fmt.Sprintf("domains=%v", cert.Domains)) http.Redirect(w, r, "/certificates?flash="+flashMsg("Certificate issued")+"&kind=success", http.StatusFound) } func (h *Web) handleCertificateUpload(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(10 << 20); err != nil { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } } name := r.FormValue("name") domains := splitLines(r.FormValue("domains")) certPEM := r.FormValue("cert_pem") keyPEM := r.FormValue("key_pem") if name == "" || certPEM == "" || keyPEM == "" { h.redirectCertError(w, r, "Name, certificate, and private key are required") return } cert, err := h.certs.UploadCustom(name, domains, []byte(certPEM), []byte(keyPEM)) if err != nil { h.redirectCertError(w, r, "Upload failed: "+err.Error()) return } logging.Audit(h.db, auth.ActorFromContext(r.Context()), "certificate_issued", cert.Name, "custom certificate uploaded") http.Redirect(w, r, "/certificates?flash="+flashMsg("Certificate uploaded")+"&kind=success", http.StatusFound) } func (h *Web) redirectCertError(w http.ResponseWriter, r *http.Request, msg string) { http.Redirect(w, r, "/certificates?flash="+flashMsg(msg)+"&kind=danger", http.StatusFound) } func (h *Web) handleCertificateRenew(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64) c, err := database.GetCertificate(h.db, id) if err != nil { http.Error(w, "not found", http.StatusNotFound) return } if c.Type != models.CertTypeLetsEncrypt { h.redirectCertError(w, r, "Only Let's Encrypt certificates can be renewed here") return } settings := h.store.Current().Settings _, err = h.certs.IssueLetsEncrypt(r.Context(), certificates.IssueLetsEncryptOptions{ Name: c.Name, Domains: c.Domains, Email: settings.ACMEEmail, Staging: settings.ACMEStaging, ChallengeType: c.ChallengeType, DNSProvider: c.DNSProvider, }) if err != nil { h.redirectCertError(w, r, "Renewal failed: "+err.Error()) return } logging.Audit(h.db, auth.ActorFromContext(r.Context()), "certificate_renewed", c.Name, "") http.Redirect(w, r, "/certificates?flash="+flashMsg("Certificate renewed")+"&kind=success", http.StatusFound) } func (h *Web) handleCertificateDelete(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64) if err := database.DeleteCertificate(h.db, id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } h.certs.LoadAll() logging.Audit(h.db, auth.ActorFromContext(r.Context()), "certificate_changed", fmt.Sprintf("id=%d", id), "deleted") http.Redirect(w, r, "/certificates?flash="+flashMsg("Certificate deleted")+"&kind=success", http.StatusFound) }