getTraderFromQuery resolves a trader from the ?trader_id= query parameter, strictly scoped to the authenticated caller. Ownership is always enforced against the caller's own trader list in the store. We deliberately never fall back to the global in-memory trader map (TraderManager holds every accou
(c *gin.Context)
| 602 | // freshly-registered user with no traders of their own could otherwise pass any |
| 603 | // other account's trader_id and read its balance, positions and AI decisions. |
| 604 | func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, string, error) { |
| 605 | userID := c.GetString("user_id") |
| 606 | traderID := c.Query("trader_id") |
| 607 | |
| 608 | // Ensure user's traders are loaded into memory. |
| 609 | if err := s.traderManager.LoadUserTradersFromStore(s.store, userID); err != nil { |
| 610 | logger.Infof("⚠️ Failed to load traders for user %s: %v", userID, err) |
| 611 | } |
| 612 | |
| 613 | // Resolve strictly from the caller's own trader list. |
| 614 | userTraders, err := s.store.Trader().List(userID) |
| 615 | if err != nil { |
| 616 | return nil, "", fmt.Errorf("failed to load traders for this account: %w", err) |
| 617 | } |
| 618 | if len(userTraders) == 0 { |
| 619 | return nil, "", fmt.Errorf("No available traders") |
| 620 | } |
| 621 | |
| 622 | if traderID == "" { |
| 623 | // No trader_id specified — default to the caller's first trader. |
| 624 | return s.traderManager, userTraders[0].ID, nil |
| 625 | } |
| 626 | |
| 627 | // A trader_id was supplied — it must belong to the caller. |
| 628 | for _, t := range userTraders { |
| 629 | if t.ID == traderID { |
| 630 | return s.traderManager, traderID, nil |
| 631 | } |
| 632 | } |
| 633 | return nil, "", fmt.Errorf("trader not found for this account") |
| 634 | } |
| 635 | |
| 636 | // authMiddleware JWT authentication middleware |
| 637 | func (s *Server) authMiddleware() gin.HandlerFunc { |
no test coverage detected