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.
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
# 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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
type ConfigProvider interface {
|
||||
CreateConfig() *AuthorizationConfig
|
||||
}
|
||||
```
|
||||
|
||||
#### Environment-based Configuration
|
||||
|
||||
```go
|
||||
// Create configuration from environment variables
|
||||
configProvider := authorization.NewEnvConfigProvider()
|
||||
config := configProvider.CreateConfig()
|
||||
```
|
||||
|
||||
#### Custom Configuration
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
```go
|
||||
// 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)
|
||||
|
||||
```go
|
||||
// File-based configuration (placeholder for future implementation)
|
||||
fileProvider := authorization.NewFileConfigProvider("config.yaml")
|
||||
config := fileProvider.CreateConfig()
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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.
|
||||
Reference in New Issue
Block a user