fix(agent-server): authenticate /v1/nodes via session tokens

This commit is contained in:
Haitao Pan 2026-02-05 17:39:43 +08:00
parent cc0ff117f6
commit fbe04d37b1
2 changed files with 55 additions and 20 deletions

View File

@ -301,14 +301,14 @@ func RegisterRoutes(r *gin.Engine, opts ...Option) {
}
registerAdminRoutes(apiGroup, h)
// Canonical user-facing agent routes under /api/agent-server/v1.
// Keep this in the authenticated user API group so dashboard requests can
// read registered nodes directly without console-side custom route logic.
agentServerGroup := apiGroup.Group("/agent-server/v1")
// Canonical user-facing agent routes.
// These endpoints use session-based auth in handler logic and intentionally
// stay outside token middleware to support dashboard session tokens.
agentServerGroup := r.Group("/api/agent-server/v1")
agentServerGroup.GET("/nodes", h.listAgentNodes)
// User agent routes - /api/agent/nodes
agentGroup := apiGroup.Group("/agent")
// Legacy alias kept for backward compatibility.
agentGroup := r.Group("/api/agent")
agentGroup.GET("/nodes", h.listAgentNodes)
}

View File

@ -44,20 +44,8 @@ type vlessNode struct {
}
func (h *handler) listAgentNodes(c *gin.Context) {
// Get current user ID to use as VLESS UUID
userID := auth.GetUserID(c)
if userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
user, err := h.store.GetUserByID(c.Request.Context(), userID)
if err != nil {
if errors.Is(err, store.ErrUserNotFound) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user_not_found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch user"})
user, ok := h.resolveAgentNodeUser(c)
if !ok {
return
}
@ -148,6 +136,53 @@ func (h *handler) listAgentNodes(c *gin.Context) {
c.JSON(http.StatusOK, nodes)
}
func (h *handler) resolveAgentNodeUser(c *gin.Context) (*store.User, bool) {
if userID := strings.TrimSpace(auth.GetUserID(c)); userID != "" && userID != "system" {
user, err := h.store.GetUserByID(c.Request.Context(), userID)
if err != nil {
if errors.Is(err, store.ErrUserNotFound) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user_not_found"})
return nil, false
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed_to_fetch_user"})
return nil, false
}
return user, true
}
token := extractToken(c.GetHeader("Authorization"))
if token == "" {
token = strings.TrimSpace(c.Query("token"))
}
if token == "" {
if cookie, err := c.Cookie(sessionCookieName); err == nil {
token = strings.TrimSpace(cookie)
}
}
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "session_token_required", "message": "session token is required"})
return nil, false
}
sess, ok := h.lookupSession(token)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid_session", "message": "session token is invalid or expired"})
return nil, false
}
user, err := h.store.GetUserByID(c.Request.Context(), sess.userID)
if err != nil {
if errors.Is(err, store.ErrUserNotFound) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user_not_found"})
return nil, false
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed_to_fetch_user"})
return nil, false
}
return user, true
}
func parseProxyNodeHosts(publicURL string, extraHosts []string) []string {
seen := make(map[string]struct{})
hosts := make([]string, 0)