133 lines
3.5 KiB
Go
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)
|
|
}
|