Introduce an endpoint for importing Vertex service account JSON keys and storing them as authentication records. Add handlers for managing WebSocket authentication configuration.
181 lines
5.5 KiB
Go
181 lines
5.5 KiB
Go
package management
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func (h *Handler) GetConfig(c *gin.Context) {
|
|
if h == nil || h.cfg == nil {
|
|
c.JSON(200, gin.H{})
|
|
return
|
|
}
|
|
cfgCopy := *h.cfg
|
|
cfgCopy.GlAPIKey = geminiKeyStringsFromConfig(h.cfg)
|
|
c.JSON(200, &cfgCopy)
|
|
}
|
|
|
|
func (h *Handler) GetConfigYAML(c *gin.Context) {
|
|
data, err := os.ReadFile(h.configFilePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "read_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
var node yaml.Node
|
|
if err := yaml.Unmarshal(data, &node); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
c.Header("Content-Type", "application/yaml; charset=utf-8")
|
|
c.Header("Vary", "format, Accept")
|
|
enc := yaml.NewEncoder(c.Writer)
|
|
enc.SetIndent(2)
|
|
_ = enc.Encode(&node)
|
|
_ = enc.Close()
|
|
}
|
|
|
|
func WriteConfig(path string, data []byte) error {
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := f.Write(data); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
if err := f.Sync(); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
return f.Close()
|
|
}
|
|
|
|
func (h *Handler) PutConfigYAML(c *gin.Context) {
|
|
body, err := io.ReadAll(c.Request.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": "cannot read request body"})
|
|
return
|
|
}
|
|
var cfg config.Config
|
|
if err := yaml.Unmarshal(body, &cfg); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()})
|
|
return
|
|
}
|
|
// Validate config using LoadConfigOptional with optional=false to enforce parsing
|
|
tmpDir := filepath.Dir(h.configFilePath)
|
|
tmpFile, err := os.CreateTemp(tmpDir, "config-validate-*.yaml")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
tempFile := tmpFile.Name()
|
|
if _, err := tmpFile.Write(body); err != nil {
|
|
tmpFile.Close()
|
|
os.Remove(tempFile)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
if err := tmpFile.Close(); err != nil {
|
|
os.Remove(tempFile)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
defer os.Remove(tempFile)
|
|
_, err = config.LoadConfigOptional(tempFile, false)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()})
|
|
return
|
|
}
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if WriteConfig(h.configFilePath, body) != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": "failed to write config"})
|
|
return
|
|
}
|
|
// Reload into handler to keep memory in sync
|
|
newCfg, err := config.LoadConfig(h.configFilePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "reload_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
h.cfg = newCfg
|
|
c.JSON(http.StatusOK, gin.H{"ok": true, "changed": []string{"config"}})
|
|
}
|
|
|
|
// GetConfigFile returns the raw config.yaml file bytes without re-encoding.
|
|
// It preserves comments and original formatting/styles.
|
|
func (h *Handler) GetConfigFile(c *gin.Context) {
|
|
data, err := os.ReadFile(h.configFilePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not_found", "message": "config file not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "read_failed", "message": err.Error()})
|
|
return
|
|
}
|
|
c.Header("Content-Type", "application/yaml; charset=utf-8")
|
|
c.Header("Cache-Control", "no-store")
|
|
c.Header("X-Content-Type-Options", "nosniff")
|
|
// Write raw bytes as-is
|
|
_, _ = c.Writer.Write(data)
|
|
}
|
|
|
|
// Debug
|
|
func (h *Handler) GetDebug(c *gin.Context) { c.JSON(200, gin.H{"debug": h.cfg.Debug}) }
|
|
func (h *Handler) PutDebug(c *gin.Context) { h.updateBoolField(c, func(v bool) { h.cfg.Debug = v }) }
|
|
|
|
// UsageStatisticsEnabled
|
|
func (h *Handler) GetUsageStatisticsEnabled(c *gin.Context) {
|
|
c.JSON(200, gin.H{"usage-statistics-enabled": h.cfg.UsageStatisticsEnabled})
|
|
}
|
|
func (h *Handler) PutUsageStatisticsEnabled(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.UsageStatisticsEnabled = v })
|
|
}
|
|
|
|
// UsageStatisticsEnabled
|
|
func (h *Handler) GetLoggingToFile(c *gin.Context) {
|
|
c.JSON(200, gin.H{"logging-to-file": h.cfg.LoggingToFile})
|
|
}
|
|
func (h *Handler) PutLoggingToFile(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.LoggingToFile = v })
|
|
}
|
|
|
|
// Request log
|
|
func (h *Handler) GetRequestLog(c *gin.Context) { c.JSON(200, gin.H{"request-log": h.cfg.RequestLog}) }
|
|
func (h *Handler) PutRequestLog(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.RequestLog = v })
|
|
}
|
|
|
|
// Websocket auth
|
|
func (h *Handler) GetWebsocketAuth(c *gin.Context) {
|
|
c.JSON(200, gin.H{"ws-auth": h.cfg.WebsocketAuth})
|
|
}
|
|
func (h *Handler) PutWebsocketAuth(c *gin.Context) {
|
|
h.updateBoolField(c, func(v bool) { h.cfg.WebsocketAuth = v })
|
|
}
|
|
|
|
// Request retry
|
|
func (h *Handler) GetRequestRetry(c *gin.Context) {
|
|
c.JSON(200, gin.H{"request-retry": h.cfg.RequestRetry})
|
|
}
|
|
func (h *Handler) PutRequestRetry(c *gin.Context) {
|
|
h.updateIntField(c, func(v int) { h.cfg.RequestRetry = v })
|
|
}
|
|
|
|
// Proxy URL
|
|
func (h *Handler) GetProxyURL(c *gin.Context) { c.JSON(200, gin.H{"proxy-url": h.cfg.ProxyURL}) }
|
|
func (h *Handler) PutProxyURL(c *gin.Context) {
|
|
h.updateStringField(c, func(v string) { h.cfg.ProxyURL = v })
|
|
}
|
|
func (h *Handler) DeleteProxyURL(c *gin.Context) {
|
|
h.cfg.ProxyURL = ""
|
|
h.persist(c)
|
|
}
|