package middleware import ( "fmt" "os" "strings" "github.com/labstack/echo/v4" "tm/internal/domain" "tm/pkg/logger" "tm/pkg/response" ) // AuthMiddleware creates JWT authentication middleware for both customers and users func AuthMiddleware(customerAuthService domain.CustomerAuthService, userAuthService domain.UserAuthService, logger logger.Logger) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // Skip authentication for public routes if isPublicRoute(c.Path(), c.Request().Method) { return next(c) } // Get Authorization header authHeader := c.Request().Header.Get("Authorization") if authHeader == "" { logger.Warn("Missing authorization header", map[string]interface{}{ "path": c.Path(), "method": c.Request().Method, }) return response.Unauthorized(c, "Authorization header required") } // Check Bearer format const bearerPrefix = "Bearer " if !strings.HasPrefix(authHeader, bearerPrefix) { logger.Warn("Invalid authorization header format", map[string]interface{}{ "path": c.Path(), "method": c.Request().Method, }) return response.Unauthorized(c, "Invalid authorization header format") } // Extract token token := strings.TrimPrefix(authHeader, bearerPrefix) if token == "" { return response.Unauthorized(c, "Token required") } // Try to validate as customer first (for mobile routes) if strings.HasPrefix(c.Path(), "/api/mobile/") || strings.HasPrefix(c.Path(), "/mobile/") { customer, err := customerAuthService.ValidateToken(c.Request().Context(), token) if err == nil && customer != nil { // Store customer in context c.Set("customer", customer) c.Set("customer_id", customer.ID.String()) c.Set("company_id", customer.CompanyID.String()) c.Set("user_type", "customer") return next(c) } } // Try to validate as panel user (for admin routes) if strings.HasPrefix(c.Path(), "/api/admin/") || strings.HasPrefix(c.Path(), "/admin/") { user, err := userAuthService.ValidateToken(c.Request().Context(), token) if err == nil && user != nil { // Store user in context c.Set("user", user) c.Set("user_id", user.ID.String()) c.Set("user_type", "user") return next(c) } } // For other routes, try both types if customer, err := customerAuthService.ValidateToken(c.Request().Context(), token); err == nil && customer != nil { c.Set("customer", customer) c.Set("customer_id", customer.ID.String()) c.Set("company_id", customer.CompanyID.String()) c.Set("user_type", "customer") return next(c) } if user, err := userAuthService.ValidateToken(c.Request().Context(), token); err == nil && user != nil { c.Set("user", user) c.Set("user_id", user.ID.String()) c.Set("user_type", "user") return next(c) } logger.Warn("Token validation failed", map[string]interface{}{ "path": c.Path(), }) return response.Unauthorized(c, "Invalid or expired token") } } } // CustomerOnlyMiddleware ensures only customers can access the route func CustomerOnlyMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { customer, ok := c.Get("customer").(*domain.Customer) if !ok || customer == nil { return response.Unauthorized(c, "Customer authentication required") } if !customer.IsActive { return response.Forbidden(c, "Account is inactive") } return next(c) } } } // UserOnlyMiddleware ensures only panel users can access the route func UserOnlyMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { user, ok := c.Get("user").(*domain.User) if !ok || user == nil { return response.Unauthorized(c, "Panel user authentication required") } if !user.IsActive { return response.Forbidden(c, "Account is inactive") } return next(c) } } } // CustomerRoleMiddleware creates role-based authorization middleware for customers func CustomerRoleMiddleware(allowedRoles ...domain.CustomerRole) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { customer, ok := c.Get("customer").(*domain.Customer) if !ok { return response.Unauthorized(c, "Customer authentication required") } // Check if customer has required role for _, role := range allowedRoles { if customer.Role == role { return next(c) } } return response.Forbidden(c, "Insufficient permissions") } } } // UserRoleMiddleware creates role-based authorization middleware for panel users func UserRoleMiddleware(allowedRoles ...domain.UserRole) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { user, ok := c.Get("user").(*domain.User) if !ok { return response.Unauthorized(c, "Panel user authentication required") } // Check if user has required role for _, role := range allowedRoles { if user.Role == role { return next(c) } } return response.Forbidden(c, "Insufficient permissions") } } } // UserPermissionMiddleware creates permission-based authorization middleware for panel users func UserPermissionMiddleware(requiredPermissions ...string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { user, ok := c.Get("user").(*domain.User) if !ok { return response.Unauthorized(c, "Panel user authentication required") } // Admin has all permissions if user.IsAdmin() { return next(c) } // Check if user has all required permissions for _, permission := range requiredPermissions { if !user.HasPermission(permission) { return response.Forbidden(c, "Insufficient permissions") } } return next(c) } } } // isPublicRoute checks if a route should be accessible without authentication func isPublicRoute(path, method string) bool { // Debug logging fmt.Printf("Checking public route: %s %s\n", method, path) // Also log to stderr to ensure we see it fmt.Fprintf(os.Stderr, "DEBUG: Checking public route: %s %s\n", method, path) publicRoutes := map[string][]string{ // Mobile customer auth routes "/api/v1/mobile/auth/register": {"POST"}, "/api/v1/mobile/auth/login": {"POST"}, "/api/v1/mobile/auth/refresh": {"POST"}, "/api/v1/mobile/auth/resend-verification": {"POST"}, "/api/mobile/auth/register": {"POST"}, "/api/mobile/auth/login": {"POST"}, "/api/mobile/auth/refresh": {"POST"}, "/api/mobile/auth/resend-verification": {"POST"}, "/mobile/auth/register": {"POST"}, "/mobile/auth/login": {"POST"}, "/mobile/auth/refresh": {"POST"}, "/mobile/auth/resend-verification": {"POST"}, // Panel user auth routes "/api/v1/admin/auth/login": {"POST"}, "/api/v1/admin/auth/refresh": {"POST"}, "/api/admin/auth/login": {"POST"}, "/api/admin/auth/refresh": {"POST"}, "/admin/auth/login": {"POST"}, "/admin/auth/refresh": {"POST"}, // Legacy auth routes (for backward compatibility) "/auth/register": {"POST"}, "/auth/login": {"POST"}, "/auth/refresh": {"POST"}, // Health and documentation routes "/health": {"GET"}, "/metrics": {"GET"}, "/api/docs": {"GET"}, "/api/docs/*": {"GET"}, "/swagger/*": {"GET"}, } if methods, exists := publicRoutes[path]; exists { for _, allowedMethod := range methods { if method == allowedMethod { return true } } } // Check for wildcard routes for route, methods := range publicRoutes { if strings.HasSuffix(route, "/*") { prefix := strings.TrimSuffix(route, "/*") if strings.HasPrefix(path, prefix) { for _, allowedMethod := range methods { if method == allowedMethod { return true } } } } } return false }