vibe-proxy/backend/internal/api/server_management.go
2026-08-27 15:02:32 +02:00

133 lines
3.5 KiB
Go

package api
import (
"errors"
"io/fs"
"net/http"
"path"
"regexp"
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset"
log "github.com/sirupsen/logrus"
)
var hashedManagementAssetPattern = regexp.MustCompile(`(^|[-.])[A-Za-z0-9_-]{8,}(\.[^.]+)+$`)
func (s *Server) registerManagementRoutes() {
if s == nil || s.engine == nil || s.mgmt == nil {
return
}
if !s.managementRoutesRegistered.CompareAndSwap(false, true) {
return
}
log.Info("management routes registered after secret key configuration")
mgmt := s.engine.Group("/v0/management")
mgmt.Use(s.managementAvailabilityMiddleware(), s.mgmt.Middleware())
{
mgmt.GET("/auth-files", s.mgmt.ListAuthFiles)
mgmt.DELETE("/auth-files", s.mgmt.DeleteAuthFile)
mgmt.POST("/codex-quota", s.mgmt.CodexQuota)
mgmt.GET("/codex-auth-url", s.mgmt.RequestCodexToken)
mgmt.GET("/get-auth-status", s.mgmt.GetAuthStatus)
mgmt.DELETE("/oauth-session", s.mgmt.CancelAuthSession)
mgmt.POST("/oauth-callback", s.mgmt.PostOAuthCallback)
}
}
func (s *Server) managementAvailabilityMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if !s.managementAvailable(c) {
return
}
c.Next()
}
}
func (s *Server) managementAvailable(c *gin.Context) bool {
if s == nil || s.cfg == nil {
c.AbortWithStatus(http.StatusNotFound)
return false
}
if !s.managementRoutesEnabled.Load() {
c.AbortWithStatus(http.StatusNotFound)
return false
}
return true
}
func (s *Server) RefreshPluginManagementRoutes() {}
func (s *Server) pluginManagementNoRoute(c *gin.Context) {
c.AbortWithStatus(http.StatusNotFound)
}
func (s *Server) serveManagementControlPanel(c *gin.Context) {
cfg := s.cfg
if cfg == nil || cfg.RemoteManagement.DisableControlPanel {
c.AbortWithStatus(http.StatusNotFound)
return
}
source, err := managementasset.Current()
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
log.WithError(err).Error("failed to resolve management control panel assets")
}
c.AbortWithStatus(http.StatusNotFound)
return
}
s.serveManagementFile(c, source, source.Entry, false)
}
func (s *Server) serveManagementAsset(c *gin.Context) {
cfg := s.cfg
if cfg == nil || cfg.RemoteManagement.DisableControlPanel {
c.AbortWithStatus(http.StatusNotFound)
return
}
assetPath := strings.TrimPrefix(c.Param("filepath"), "/")
if assetPath == "" || strings.Contains(assetPath, `\`) || !fs.ValidPath(assetPath) {
c.AbortWithStatus(http.StatusNotFound)
return
}
source, err := managementasset.Current()
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
log.WithError(err).Error("failed to resolve management control panel assets")
}
c.AbortWithStatus(http.StatusNotFound)
return
}
s.serveManagementFile(c, source, assetPath, true)
}
func (s *Server) serveManagementFile(c *gin.Context, source *managementasset.Source, name string, cacheHashed bool) {
file, err := source.FileSystem.Open(name)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
defer func() {
if errClose := file.Close(); errClose != nil {
log.WithError(errClose).Debug("failed to close management asset")
}
}()
info, err := file.Stat()
if err != nil || info.IsDir() {
c.AbortWithStatus(http.StatusNotFound)
return
}
if !cacheHashed {
c.Header("Content-Type", "text/html; charset=utf-8")
} else if hashedManagementAssetPattern.MatchString(path.Base(name)) {
c.Header("Cache-Control", "public, max-age=31536000, immutable")
}
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
}