6498903a7c
- CreateCredential was setting cred.Data = cred.Masked directly on the store pointer, corrupting stored credentials with masked data - Fixed by using a value copy for API response - Also fix nextID starts at 1 to avoid ID=0 issue
184 lines
4.7 KiB
Go
184 lines
4.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"auto-ssl/config"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type CredentialHandler struct{}
|
|
|
|
func NewCredentialHandler() *CredentialHandler {
|
|
return &CredentialHandler{}
|
|
}
|
|
|
|
type CreateCredentialRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Type config.CredentialType `json:"type" binding:"required"`
|
|
Data string `json:"data" binding:"required"` // JSON key-value pairs
|
|
}
|
|
|
|
type UpdateCredentialRequest struct {
|
|
Name string `json:"name"`
|
|
Type config.CredentialType `json:"type"`
|
|
Data string `json:"data"` // JSON key-value pairs (or "********" to keep)
|
|
}
|
|
|
|
// ListCredentials returns all credentials (without secrets)
|
|
func (h *CredentialHandler) ListCredentials(c *gin.Context) {
|
|
creds := config.CredStore.GetAll()
|
|
c.JSON(http.StatusOK, creds)
|
|
}
|
|
|
|
// GetCredential returns a single credential
|
|
func (h *CredentialHandler) GetCredential(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
|
|
}
|
|
|
|
cred := config.CredStore.GetByID(uint(id))
|
|
if cred == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "credential not found"})
|
|
return
|
|
}
|
|
|
|
// Never expose raw secrets in API
|
|
cred.Data = cred.Masked
|
|
c.JSON(http.StatusOK, cred)
|
|
}
|
|
|
|
// CreateCredential creates a new credential
|
|
func (h *CredentialHandler) CreateCredential(c *gin.Context) {
|
|
var req CreateCredentialRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Validate JSON data
|
|
var testData map[string]string
|
|
if err := json.Unmarshal([]byte(req.Data), &testData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "data must be valid JSON key-value pairs"})
|
|
return
|
|
}
|
|
|
|
// Validate fields for the provider type
|
|
fields := config.CredentialTypeMeta(req.Type)
|
|
for _, f := range fields {
|
|
if f.Required {
|
|
if _, ok := testData[f.Key]; !ok || testData[f.Key] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": f.Label + " 是必填项"})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
cred := &config.Credential{
|
|
Name: req.Name,
|
|
Type: req.Type,
|
|
Data: req.Data,
|
|
}
|
|
|
|
if err := config.CredStore.Create(cred); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Return without secrets (use a copy to avoid mutating store)
|
|
resp := *cred
|
|
resp.Data = cred.Masked
|
|
c.JSON(http.StatusCreated, &resp)
|
|
}
|
|
|
|
// UpdateCredential updates a credential
|
|
func (h *CredentialHandler) UpdateCredential(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
|
|
}
|
|
|
|
existing := config.CredStore.GetByID(uint(id))
|
|
if existing == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "credential not found"})
|
|
return
|
|
}
|
|
|
|
var req UpdateCredentialRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if req.Name != "" {
|
|
existing.Name = req.Name
|
|
}
|
|
if req.Type != "" {
|
|
existing.Type = req.Type
|
|
}
|
|
if req.Data != "" {
|
|
// If it's the masked placeholder, keep existing data
|
|
if req.Data == "********" {
|
|
existing.Data = existing.Data
|
|
} else {
|
|
var testData map[string]string
|
|
if err := json.Unmarshal([]byte(req.Data), &testData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "data must be valid JSON"})
|
|
return
|
|
}
|
|
existing.Data = req.Data
|
|
}
|
|
}
|
|
|
|
if err := config.CredStore.Update(existing); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
existing.Data = existing.Masked
|
|
c.JSON(http.StatusOK, existing)
|
|
}
|
|
|
|
// DeleteCredential deletes a credential
|
|
func (h *CredentialHandler) DeleteCredential(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.CredStore.Delete(uint(id)); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "credential deleted"})
|
|
}
|
|
|
|
// GetCredentialTypes returns available credential types and their fields
|
|
func (h *CredentialHandler) GetCredentialTypes(c *gin.Context) {
|
|
types := []gin.H{
|
|
{
|
|
"type": config.CredentialAliDNS,
|
|
"label": "阿里云 DNS (Aliyun)",
|
|
"fields": config.CredentialTypeMeta(config.CredentialAliDNS),
|
|
},
|
|
{
|
|
"type": config.CredentialCloudflare,
|
|
"label": "Cloudflare",
|
|
"fields": config.CredentialTypeMeta(config.CredentialCloudflare),
|
|
},
|
|
{
|
|
"type": config.CredentialDNSPod,
|
|
"label": "DNSPod",
|
|
"fields": config.CredentialTypeMeta(config.CredentialDNSPod),
|
|
},
|
|
}
|
|
c.JSON(http.StatusOK, types)
|
|
}
|