50c018af62
continuous-integration/drone/push Build is passing
- Introduced CompanyContextMiddleware to resolve the active company context for customer requests, ensuring that tender recommendations and company-scoped APIs remain in sync with the database. - Updated public routes to utilize the new CompanyContextMiddleware alongside the existing AuthMiddleware, improving the handling of company-specific requests. - Added unit tests for the pickActiveCompanyID function to validate the logic for selecting the appropriate company context based on customer assignments and requested company IDs. This update enhances the accuracy and reliability of company context management in the application, improving user experience and data consistency.
115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
package customer
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"tm/pkg/audit"
|
|
"tm/pkg/response"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// AuthMiddleware validates JWT access tokens and extracts customer information
|
|
func (h *Handler) AuthMiddleware() echo.MiddlewareFunc {
|
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
// Extract token from Authorization header
|
|
authHeader := c.Request().Header.Get("Authorization")
|
|
if authHeader == "" {
|
|
return response.Unauthorized(c, "Authorization header required")
|
|
}
|
|
|
|
// Check if it's a Bearer token
|
|
if !strings.HasPrefix(authHeader, "Bearer ") {
|
|
return response.Unauthorized(c, "Invalid authorization format. Use 'Bearer <token>'")
|
|
}
|
|
|
|
// Extract the token
|
|
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
|
|
|
// Validate the access token using authorization service
|
|
validationResult, err := h.authService.ValidateAccessToken(tokenString)
|
|
if err != nil {
|
|
h.logger.Error("Token validation error", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return response.Unauthorized(c, "Invalid token")
|
|
}
|
|
|
|
if !validationResult.Valid {
|
|
if validationResult.Expired {
|
|
return response.Unauthorized(c, "Token expired")
|
|
}
|
|
return response.Unauthorized(c, validationResult.Error)
|
|
}
|
|
|
|
// Extract customer information from token
|
|
customerID := validationResult.Claims.UserID
|
|
|
|
// Store customer information in context for handlers to use
|
|
c.Set("customer_id", customerID)
|
|
c.Set("customer_email", validationResult.Claims.Email)
|
|
c.Set("customer_role", validationResult.Claims.Role)
|
|
c.Set("company_id", validationResult.Claims.CompanyID)
|
|
c.Set("access_token", tokenString)
|
|
|
|
audit.EnrichEchoContext(c, audit.ActorTypeCustomer)
|
|
|
|
// Continue to next handler
|
|
return next(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// CompanyContextMiddleware resolves company_id from current customer assignments.
|
|
// JWT company_id can be stale after admin reassigns companies; this keeps tender
|
|
// recommendations and other company-scoped APIs in sync with the database.
|
|
func (h *Handler) CompanyContextMiddleware() echo.MiddlewareFunc {
|
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
customerID, err := GetCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
tokenCompanyID, _ := c.Get("company_id").(string)
|
|
requestedCompanyID := strings.TrimSpace(c.QueryParam("company_id"))
|
|
|
|
companyID, err := h.service.ResolveActiveCompanyID(
|
|
c.Request().Context(),
|
|
customerID,
|
|
tokenCompanyID,
|
|
requestedCompanyID,
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, errCompanyNotAssigned) {
|
|
return response.Forbidden(c, "Company is not assigned to customer")
|
|
}
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
c.Set("company_id", companyID)
|
|
return next(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetCustomerIDFromContext extracts customer ID from Echo context
|
|
func GetCustomerIDFromContext(c echo.Context) (string, error) {
|
|
customerID, ok := c.Get("customer_id").(string)
|
|
if !ok {
|
|
return "", echo.NewHTTPError(http.StatusUnauthorized, "Customer ID not found in context")
|
|
}
|
|
return customerID, nil
|
|
}
|
|
|
|
// GetAccessTokenFromContext extracts access token from Echo context
|
|
func GetAccessTokenFromContext(c echo.Context) (string, error) {
|
|
accessToken, ok := c.Get("access_token").(string)
|
|
if !ok {
|
|
return "", echo.NewHTTPError(http.StatusUnauthorized, "Access token not found in context")
|
|
}
|
|
return accessToken, nil
|
|
}
|