accounts/api/agent_server.go
2026-04-12 13:42:48 +08:00

168 lines
4.3 KiB
Go

package api
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"account/internal/agentproto"
"account/internal/agentserver"
"account/internal/store"
"account/internal/xrayconfig"
)
const agentIDHeader = "X-Agent-ID"
func (h *handler) listAgentUsers(c *gin.Context) {
if h.agentRegistry == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "agent_registry_unavailable"})
return
}
token := extractToken(c.GetHeader("Authorization"))
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing_token"})
return
}
credIdentity, ok := h.agentRegistry.Authenticate(token)
if !ok || credIdentity == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid_token"})
return
}
agentID := strings.TrimSpace(c.GetHeader(agentIDHeader))
if agentID == "" {
agentID = strings.TrimSpace(c.Query("agentId"))
}
if agentID == "" {
agentID = credIdentity.ID
}
identity := *credIdentity
if agentID != "" && agentID != identity.ID {
// Shared token scenario: register a concrete agent id so sandbox bindings can target it.
identity = h.agentRegistry.RegisterAgent(agentID, identity.Groups)
}
now := time.Now().UTC()
clients := make([]xrayconfig.Client, 0, 16)
users, err := h.store.ListUsers(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "list_users_failed"})
return
}
for _, u := range users {
if !u.Active {
continue
}
email := strings.ToLower(strings.TrimSpace(u.Email))
// Sandbox is a special demo identity with a rotating proxy UUID.
// Always include it (and rotate on read if needed), so every node/region
// can sync a consistent sandbox client for the Guest experience.
if email == sandboxUserEmail {
sandboxUser := u
_ = h.ensureSandboxProxyUUID(c.Request.Context(), &sandboxUser)
id := strings.TrimSpace(sandboxUser.ProxyUUID)
if id == "" {
id = strings.TrimSpace(sandboxUser.ID)
}
if id != "" {
clients = append(clients, xrayconfig.Client{
ID: id,
Email: strings.ToLower(strings.TrimSpace(sandboxUser.Email)),
Flow: xrayconfig.DefaultFlow,
})
}
continue
}
id := strings.TrimSpace(u.ProxyUUID)
if id == "" {
id = strings.TrimSpace(u.ID)
}
if id == "" {
continue
}
clients = append(clients, xrayconfig.Client{
ID: id,
Email: strings.ToLower(strings.TrimSpace(u.Email)),
Flow: xrayconfig.DefaultFlow,
})
}
c.JSON(http.StatusOK, agentproto.ClientListResponse{
Clients: clients,
Total: len(clients),
GeneratedAt: now,
})
}
func (h *handler) reportAgentStatus(c *gin.Context) {
if h.agentRegistry == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "agent_registry_unavailable"})
return
}
token := extractToken(c.GetHeader("Authorization"))
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing_token"})
return
}
credIdentity, ok := h.agentRegistry.Authenticate(token)
if !ok || credIdentity == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid_token"})
return
}
var report agentproto.StatusReport
if err := c.ShouldBindJSON(&report); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_request", "message": err.Error()})
return
}
agentID := strings.TrimSpace(report.AgentID)
if agentID == "" {
agentID = strings.TrimSpace(c.GetHeader(agentIDHeader))
}
if agentID == "" {
agentID = credIdentity.ID
}
identity := *credIdentity
if agentID != "" && agentID != identity.ID {
identity = h.agentRegistry.RegisterAgent(agentID, identity.Groups)
}
// Ensure report uses the resolved agent id.
report.AgentID = identity.ID
h.agentRegistry.ReportStatus(identity, report)
if h.store != nil {
nodeID := strings.TrimSpace(report.Xray.NodeID)
if nodeID == "" {
nodeID = identity.ID
}
_ = h.store.UpsertNodeHealthSnapshot(c.Request.Context(), &store.NodeHealthSnapshot{
NodeID: nodeID,
Region: strings.TrimSpace(report.Xray.Region),
LineCode: strings.TrimSpace(report.Xray.LineCode),
PricingGroup: strings.TrimSpace(report.Xray.PricingGroup),
StatsEnabled: report.Xray.StatsEnabled,
XrayRevision: strings.TrimSpace(report.Xray.XrayRevision),
Healthy: report.Healthy,
SampledAt: time.Now().UTC(),
})
}
c.Status(http.StatusNoContent)
}
var _ = agentserver.Identity{}