Files
tm_back/pkg/middleware/auth.go
T
2025-07-27 16:20:21 +03:30

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
}