271 lines
8.0 KiB
Go
271 lines
8.0 KiB
Go
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
|
|
}
|