package controller import ( "net/http" "strings" "github.com/mhsanaei/3x-ui/v2/web/middleware" "github.com/mhsanaei/3x-ui/v2/web/service" "github.com/mhsanaei/3x-ui/v2/web/session" "github.com/gin-gonic/gin" ) // APIController handles the main API routes for the 3x-ui panel, including inbounds and server management. type APIController struct { BaseController inboundController *InboundController serverController *ServerController nodeController *NodeController settingService service.SettingService userService service.UserService Tgbot service.Tgbot } // NewAPIController creates a new APIController instance and initializes its routes. func NewAPIController(g *gin.RouterGroup, customGeo *service.CustomGeoService) *APIController { a := &APIController{} a.initRouter(g, customGeo) return a } // checkAPIAuth is a middleware that returns 404 for unauthenticated API requests // to hide the existence of API endpoints from unauthorized users. // // Two auth paths are accepted: // 1. Authorization: Bearer — used by remote central panels // polling this instance as a node. Matches via constant-time compare. // Sets c.Set("api_authed", true) so CSRFMiddleware can short-circuit. // 2. Existing session cookie — used by browsers logged into the panel UI. // // Anything else falls through to a 404 so the API endpoints remain hidden. func (a *APIController) checkAPIAuth(c *gin.Context) { auth := c.GetHeader("Authorization") if strings.HasPrefix(auth, "Bearer ") { tok := strings.TrimPrefix(auth, "Bearer ") if a.settingService.MatchApiToken(tok) { // Handlers like InboundController.addInbound assume a logged-in // user (inbound.UserId = user.Id). Bearer callers have no // session, so attach the first user as a fallback. Single-user // panels are the norm here. if u, err := a.userService.GetFirstUser(); err == nil { session.SetAPIAuthUser(c, u) } c.Set("api_authed", true) c.Next() return } } if !session.IsLogin(c) { c.AbortWithStatus(http.StatusNotFound) return } c.Next() } // initRouter sets up the API routes for inbounds, server, and other endpoints. func (a *APIController) initRouter(g *gin.RouterGroup, customGeo *service.CustomGeoService) { // Main API group api := g.Group("/panel/api") api.Use(a.checkAPIAuth) api.Use(middleware.CSRFMiddleware()) // Inbounds API inbounds := api.Group("/inbounds") a.inboundController = NewInboundController(inbounds) // Server API server := api.Group("/server") a.serverController = NewServerController(server) // Nodes API — multi-panel management nodes := api.Group("/nodes") a.nodeController = NewNodeController(nodes) NewCustomGeoController(api.Group("/custom-geo"), customGeo) // Extra routes api.GET("/backuptotgbot", a.BackuptoTgbot) } // BackuptoTgbot sends a backup of the panel data to Telegram bot admins. func (a *APIController) BackuptoTgbot(c *gin.Context) { a.Tgbot.SendBackupToAdmins() }