Files
tm_back/pkg/authorization/README.md
T
n.nakhostin 98f581ec97 Enhance cursor rules and add user management functionality
- Updated cursor rules to emphasize the importance of dependency injection and logging practices.
- Introduced a new user management domain, including entities, services, handlers, and validation.
- Implemented user authentication features such as login, registration, and role-based access control.
- Added Redis integration for token management and caching.
- Enhanced API documentation with Swagger for user-related endpoints.
- Updated configuration to support new JWT settings and Redis connection parameters.
2025-08-10 14:09:17 +03:30

9.3 KiB

Authorization Package

This package provides JWT-based authorization services for the Tender Management system, following Clean Architecture principles and best practices.

Features

  • JWT Token Management: Generate and validate access and refresh tokens
  • Middleware Support: Ready-to-use Echo middleware for authentication and authorization
  • Role-Based Access Control: Built-in role validation middleware
  • Company Scoping: Company-level access control middleware
  • Token Blacklisting: Support for token invalidation (Redis-ready)
  • Injectable Configuration: Dependency injection for flexible configuration management
  • Configuration Providers: Multiple ways to create configurations (environment, custom, hybrid)
  • Security Best Practices: Secure token generation and validation

Quick Start

1. Basic Usage

package main

import (
    "tm/pkg/authorization"
    "tm/pkg/logger"
)

func main() {
    // Create logger
    logger := logger.NewLogger()
    
    // Create configuration provider (environment-based)
    configProvider := authorization.NewEnvConfigProvider()
    config := configProvider.CreateConfig()
    
    // Create authorization service
    authService := authorization.NewAuthorizationService(config, logger)
    
    // Generate tokens
    tokenPair, err := authService.GenerateTokenPair(
        "user123",
        "user@example.com", 
        "admin",
        "company456",
    )
    if err != nil {
        log.Fatal(err)
    }
    
    // Validate tokens
    result, err := authService.ValidateAccessToken(tokenPair.AccessToken)
    if err != nil {
        log.Fatal(err)
    }
    
    if result.Valid {
        fmt.Printf("User ID: %s, Role: %s\n", result.Claims.UserID, result.Claims.Role)
    }
}

2. Using with Echo Framework

package main

import (
    "github.com/labstack/echo/v4"
    "tm/pkg/authorization"
    "tm/pkg/logger"
)

func main() {
    e := echo.New()
    
    // Setup authorization
    logger := logger.NewLogger()
    configProvider := authorization.NewEnvConfigProvider()
    config := configProvider.CreateConfig()
    authService := authorization.NewAuthorizationService(config, logger)
    
    // Public routes
    e.POST("/login", loginHandler)
    
    // Protected routes
    protected := e.Group("/api")
    protected.Use(authorization.AuthMiddleware(authService))
    {
        protected.GET("/profile", getProfileHandler)
        protected.PUT("/profile", updateProfileHandler)
    }
    
    // Admin routes
    admin := protected.Group("/admin")
    admin.Use(authorization.AdminMiddleware(authService))
    {
        admin.POST("/users", createUserHandler)
        admin.GET("/users", listUsersHandler)
    }
    
    // Company-scoped routes
    company := protected.Group("/company/:company_id")
    company.Use(authorization.CompanyMiddleware(authService))
    {
        company.GET("/tenders", getCompanyTendersHandler)
    }
    
    e.Start(":8080")
}

3. Configuration Management

The authorization package uses dependency injection for configuration. You can create configurations using different providers:

Configuration Providers

The package provides several configuration providers that implement the ConfigProvider interface:

type ConfigProvider interface {
    CreateConfig() *AuthorizationConfig
}

Environment-based Configuration

// Create configuration from environment variables
configProvider := authorization.NewEnvConfigProvider()
config := configProvider.CreateConfig()

Custom Configuration

// Create custom configuration
customConfig := &authorization.AuthorizationConfig{
    AccessTokenSecret:  "my-secret-key",
    RefreshTokenSecret: "my-refresh-secret",
    AccessTokenExpiry:  30 * time.Minute,
    RefreshTokenExpiry: 14 * 24 * time.Hour,
    Issuer:             "my-app",
    Audience:           "my-api",
}

configProvider := authorization.NewCustomConfigProvider(customConfig)
config := configProvider.CreateConfig()

Hybrid Configuration

// Combine multiple configuration sources with priority
envProvider := authorization.NewEnvConfigProvider()
customProvider := authorization.NewCustomConfigProvider(customConfig)
hybridProvider := authorization.NewHybridConfigProvider(envProvider, customProvider)
config := hybridProvider.CreateConfig()

// The hybrid provider applies configurations in order:
// 1. Environment variables (base)
// 2. Custom overrides (priority)

File-based Configuration (Future)

// File-based configuration (placeholder for future implementation)
fileProvider := authorization.NewFileConfigProvider("config.yaml")
config := fileProvider.CreateConfig()

Environment Variables

# Required
export TM_ACCESS_TOKEN_SECRET="your-super-secret-access-key-here"
export TM_REFRESH_TOKEN_SECRET="your-super-secret-refresh-key-here"

# Optional (with defaults)
export TM_ACCESS_TOKEN_EXPIRY="15m"
export TM_REFRESH_TOKEN_EXPIRY="168h"  # 7 days
export TM_JWT_ISSUER="tender-management"
export TM_JWT_AUDIENCE="tender-management-api"

API Reference

AuthorizationService Interface

type AuthorizationService interface {
    // Generate tokens
    GenerateTokenPair(userID, email, role, companyID string) (*TokenPair, error)
    GenerateAccessToken(userID, email, role, companyID string) (string, error)
    GenerateRefreshToken(userID, email, role, companyID string) (string, error)
    
    // Validate tokens
    ValidateToken(tokenString string) (*TokenValidationResult, error)
    ValidateAccessToken(tokenString string) (*TokenValidationResult, error)
    ValidateRefreshToken(tokenString string) (*TokenValidationResult, error)
    
    // Token management
    RefreshAccessToken(refreshToken string) (string, error)
    InvalidateToken(tokenString string) error
    IsTokenBlacklisted(tokenString string) (bool, error)
}

Middleware Functions

// Authentication middleware
AuthMiddleware(authService AuthorizationService) echo.MiddlewareFunc

// Role-based access control
RoleMiddleware(authService AuthorizationService, requiredRoles ...string) echo.MiddlewareFunc

// Admin-only access
AdminMiddleware(authService AuthorizationService) echo.MiddlewareFunc

// Company-scoped access
CompanyMiddleware(authService AuthorizationService) echo.MiddlewareFunc

// Optional authentication
OptionalAuthMiddleware(authService AuthorizationService) echo.MiddlewareFunc

Context Helpers

// Extract user information from context
userID, email, role, companyID, authenticated := GetUserFromContext(c)

// Extract token claims
claims := GetTokenClaimsFromContext(c)

// Check authentication status
if IsAuthenticated(c) {
    // User is authenticated
}

Token Structure

Access Token Claims

{
  "user_id": "uuid-string",
  "email": "user@example.com",
  "role": "admin",
  "company_id": "company-uuid",
  "token_type": "access",
  "iss": "tender-management",
  "sub": "uuid-string",
  "aud": ["tender-management-api"],
  "exp": 1640995200,
  "nbf": 1640994300,
  "iat": 1640994300
}

Refresh Token Claims

{
  "user_id": "uuid-string",
  "email": "user@example.com",
  "role": "admin",
  "company_id": "company-uuid",
  "token_type": "refresh",
  "iss": "tender-management",
  "sub": "uuid-string",
  "aud": ["tender-management-api"],
  "exp": 1641600000,
  "nbf": 1640994300,
  "iat": 1640994300
}

Security Features

Token Security

  • Separate Secrets: Access and refresh tokens use different signing secrets
  • Short Expiry: Access tokens expire quickly (default: 15 minutes)
  • Long Expiry: Refresh tokens last longer (default: 7 days)
  • Type Validation: Tokens are validated against their intended type
  • Blacklist Support: Tokens can be invalidated and blacklisted

Best Practices

  • Environment Variables: Never hardcode secrets in code
  • Strong Secrets: Use cryptographically strong random secrets
  • HTTPS Only: Always use HTTPS in production
  • Token Rotation: Implement refresh token rotation for security
  • Audit Logging: Log all authentication and authorization events

Configuration Validation

The package automatically validates configuration on startup:

config := authorization.LoadConfigFromEnv()
if err := config.ValidateConfig(); err != nil {
    log.Fatal("Invalid authorization configuration:", err)
}

Validation checks:

  • Non-empty secrets
  • Valid expiry durations
  • Access token expiry < refresh token expiry
  • Non-empty issuer and audience

Error Handling

All functions return descriptive errors:

result, err := authService.ValidateAccessToken(token)
if err != nil {
    // Handle validation error
    return err
}

if !result.Valid {
    if result.Expired {
        return response.Unauthorized(c, "Token has expired")
    }
    return response.Unauthorized(c, result.Error)
}

Future Enhancements

  • Redis Integration: Full token blacklist implementation
  • Rate Limiting: Token generation rate limiting
  • Audit Trail: Comprehensive authentication audit logging
  • Multi-Tenant: Enhanced company-level security
  • OAuth2 Support: Integration with external OAuth providers

Dependencies

  • github.com/golang-jwt/jwt/v5 - JWT implementation
  • github.com/labstack/echo/v4 - HTTP framework
  • tm/pkg/logger - Internal logging package
  • tm/pkg/response - Internal response package

License

This package is part of the Tender Management system and follows the same license terms.