Files
vibeproxy/internal/api/certificates.go
T
owenandClaude Sonnet 5 ba1f7c4c03 Initial vibeproxy implementation
Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go,
with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS
framework), and a REST API under /api/v1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
2026-08-17 10:28:59 -05:00

154 lines
4.5 KiB
Go

package api
import (
"fmt"
"net/http"
"github.com/owen/vibeproxy/internal/certificates"
"github.com/owen/vibeproxy/internal/database"
"github.com/owen/vibeproxy/internal/models"
)
func (a *API) listCertificates(w http.ResponseWriter, r *http.Request) {
certs, err := database.ListCertificates(a.db)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, certs)
}
func (a *API) getCertificate(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
c, err := database.GetCertificate(a.db, id)
if err != nil {
writeError(w, http.StatusNotFound, "certificate not found")
return
}
writeJSON(w, http.StatusOK, c)
}
type issueCertRequest struct {
Name string `json:"name"`
Domains []string `json:"domains"`
Email string `json:"email"`
Staging bool `json:"staging"`
ChallengeType models.ChallengeType `json:"challenge_type"`
DNSProvider string `json:"dns_provider"`
}
func (a *API) issueCertificate(w http.ResponseWriter, r *http.Request) {
var req issueCertRequest
if !decodeJSON(w, r, &req) {
return
}
if req.Name == "" || len(req.Domains) == 0 {
writeError(w, http.StatusBadRequest, "name and domains are required")
return
}
if req.ChallengeType == "" {
req.ChallengeType = models.ChallengeHTTP01
}
settings := a.store.Current().Settings
email := req.Email
if email == "" {
email = settings.ACMEEmail
}
cert, err := a.certs.IssueLetsEncrypt(r.Context(), certOpts(req, email))
if err != nil {
writeError(w, http.StatusBadGateway, err.Error())
return
}
audit(a.db, r, "certificate_issued", cert.Name, fmt.Sprintf("domains=%v challenge=%s", cert.Domains, cert.ChallengeType))
writeJSON(w, http.StatusCreated, cert)
}
func certOpts(req issueCertRequest, email string) certificates.IssueLetsEncryptOptions {
return certificates.IssueLetsEncryptOptions{
Name: req.Name,
Domains: req.Domains,
Email: email,
Staging: req.Staging,
ChallengeType: req.ChallengeType,
DNSProvider: req.DNSProvider,
}
}
func (a *API) uploadCertificate(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Domains []string `json:"domains"`
CertPEM string `json:"cert_pem"`
KeyPEM string `json:"key_pem"`
}
if !decodeJSON(w, r, &req) {
return
}
if req.Name == "" || req.CertPEM == "" || req.KeyPEM == "" {
writeError(w, http.StatusBadRequest, "name, cert_pem, and key_pem are required")
return
}
cert, err := a.certs.UploadCustom(req.Name, req.Domains, []byte(req.CertPEM), []byte(req.KeyPEM))
if err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
audit(a.db, r, "certificate_issued", cert.Name, "custom certificate uploaded")
writeJSON(w, http.StatusCreated, cert)
}
func (a *API) renewCertificate(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
c, err := database.GetCertificate(a.db, id)
if err != nil {
writeError(w, http.StatusNotFound, "certificate not found")
return
}
if c.Type != models.CertTypeLetsEncrypt {
writeError(w, http.StatusBadRequest, "only Let's Encrypt certificates can be renewed")
return
}
settings := a.store.Current().Settings
updated, err := a.certs.IssueLetsEncrypt(r.Context(), certOptsFromModel(*c, settings.ACMEEmail, settings.ACMEStaging))
if err != nil {
writeError(w, http.StatusBadGateway, err.Error())
return
}
audit(a.db, r, "certificate_renewed", updated.Name, "")
writeJSON(w, http.StatusOK, updated)
}
func certOptsFromModel(c models.Certificate, email string, staging bool) certificates.IssueLetsEncryptOptions {
return certificates.IssueLetsEncryptOptions{
Name: c.Name,
Domains: c.Domains,
Email: email,
Staging: staging,
ChallengeType: c.ChallengeType,
DNSProvider: c.DNSProvider,
}
}
func (a *API) deleteCertificate(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
if err := database.DeleteCertificate(a.db, id); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
a.certs.LoadAll()
audit(a.db, r, "certificate_changed", fmt.Sprintf("id=%d", id), "deleted")
w.WriteHeader(http.StatusNoContent)
}