mirror of
https://github.com/Fishwaldo/auth2.git
synced 2025-06-03 12:21:22 +00:00
Initial commit: Project setup phase 1.1
Completed Phase 1.1 of the project setup with the following components: - Initialized Go module with Go 1.24 - Set up logging with log/slog - Created error types and handling mechanisms - Implemented configuration structures - Created directory structure for the project
This commit is contained in:
commit
719aee56e8
10 changed files with 1993 additions and 0 deletions
168
CLAUDE.md
Normal file
168
CLAUDE.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
# Authentication Library Implementation Guide
|
||||
|
||||
This document provides specifications for implementing a comprehensive, modular authentication library in Go. The implementation must be complete and production-ready.
|
||||
|
||||
## Core Requirements
|
||||
|
||||
Your implementation MUST include ALL of the following:
|
||||
|
||||
1. **Pluggable Architecture**
|
||||
- Create a modular system with clear interfaces for all components
|
||||
- Support pluggable primary authentication methods
|
||||
- Support pluggable MFA (multi-factor authentication) methods
|
||||
- Allow for custom implementation of any component
|
||||
|
||||
2. **Authentication Methods**
|
||||
- **Primary Authentication**:
|
||||
- Username/Password with secure password hashing
|
||||
- OAuth2 support (Google, GitHub, Microsoft, Facebook at minimum)
|
||||
- SAML authentication
|
||||
- **MFA Methods**:
|
||||
- TOTP (Time-based One-Time Password)
|
||||
- FIDO2/WebAuthn (passkeys)
|
||||
- Email OTP (One-Time Password)
|
||||
- Backup codes
|
||||
|
||||
3. **Session Management**
|
||||
- Implement Cookie-based sessions
|
||||
- JWT token authentication
|
||||
- Bearer token authentication
|
||||
- Secure session revocation and refresh mechanisms
|
||||
|
||||
4. **Storage Adapters**
|
||||
- Standard library SQL interface
|
||||
- GORM integration
|
||||
- Ent ORM integration
|
||||
- In-memory implementation for testing
|
||||
|
||||
5. **HTTP Framework Integration**
|
||||
- Create adapters for ALL of these frameworks:
|
||||
- Standard library (net/http)
|
||||
- Chi router
|
||||
- Echo framework
|
||||
- Fiber
|
||||
- Gin
|
||||
- Gorilla mux
|
||||
- httprouter
|
||||
- Huma
|
||||
- FastHTTP
|
||||
|
||||
6. **Security Features**
|
||||
- Brute force protection with configurable limits
|
||||
- Rate limiting
|
||||
- CSRF protection
|
||||
- Secure cookie handling
|
||||
- User account locking/unlocking
|
||||
- Password reset functionality
|
||||
- Account recovery options
|
||||
|
||||
7. **RBAC (Role-Based Access Control)**
|
||||
- Permission management
|
||||
- Role assignment
|
||||
- Group-based permissions
|
||||
- Resource access control
|
||||
|
||||
8. **Developer API**
|
||||
- Clean, intuitive API
|
||||
- Consistent error handling
|
||||
- Comprehensive documentation
|
||||
- Minimal boilerplate for common use cases
|
||||
|
||||
9. **Testing**
|
||||
- Black box unit tests for ALL components
|
||||
- Mock implementations for testing
|
||||
- Test utilities to simplify testing
|
||||
- Test coverage > 80%
|
||||
- Deterministic tests (no reliance on time, randomness)
|
||||
- Table-driven tests for comprehensive scenarios
|
||||
|
||||
## Architecture Guidelines
|
||||
|
||||
1. **Clean Architecture**
|
||||
- Separate domain logic from infrastructure
|
||||
- Use interfaces (ports) for all dependencies
|
||||
- Implement adapters for specific technologies
|
||||
|
||||
2. **Hexagonal Design**
|
||||
- Core domain should be independent of external systems
|
||||
- Use dependency injection throughout
|
||||
- Clear boundaries between application layers
|
||||
|
||||
3. **Error Handling**
|
||||
- Custom error types for specific scenarios
|
||||
- Proper error wrapping with context
|
||||
- Exported errors for application testing
|
||||
|
||||
4. **Concurrency**
|
||||
- Use goroutines and channels appropriately
|
||||
- Context propagation for cancellation
|
||||
- Thread-safe implementations
|
||||
|
||||
5. **Logging**
|
||||
- Structured logging with log/slog
|
||||
- Appropriate log levels
|
||||
- Contextual information in logs
|
||||
- Proper redaction of sensitive data
|
||||
|
||||
## Implementation Details
|
||||
|
||||
1. **User Management**
|
||||
- Registration flow
|
||||
- Account verification
|
||||
- Profile management
|
||||
- Account status tracking
|
||||
|
||||
2. **Authentication Flow**
|
||||
- Registration
|
||||
- Login
|
||||
- Logout
|
||||
- MFA enrollment and verification
|
||||
- Account recovery
|
||||
- Password reset
|
||||
|
||||
3. **Session Handling**
|
||||
- Session creation
|
||||
- Session validation
|
||||
- Session expiration and renewal
|
||||
- Session revocation
|
||||
|
||||
4. **OAuth Integration**
|
||||
- OAuth2 provider configuration
|
||||
- OAuth2 callback handling
|
||||
- User profile mapping
|
||||
- Token management
|
||||
|
||||
5. **RBAC Implementation**
|
||||
- Permission definition and assignment
|
||||
- Role creation and management
|
||||
- User-role relationships
|
||||
- Permission checking middleware
|
||||
|
||||
6. **Security Measures**
|
||||
- Rate limiting implementation
|
||||
- Brute force detection and prevention
|
||||
- Security event logging
|
||||
- Audit trail
|
||||
|
||||
## Package Structure
|
||||
|
||||
1. Consult @docs/DESIGN.md
|
||||
|
||||
## CRITICAL IMPLEMENTATION REQUIREMENTS
|
||||
|
||||
1. The implementation MUST be complete and production-ready
|
||||
2. Every feature MUST be fully implemented, not stubbed
|
||||
3. All security features MUST be properly implemented (not placeholder)
|
||||
4. Every component MUST have thorough unit tests
|
||||
5. All interfaces MUST be well-defined with proper documentation
|
||||
6. Error handling MUST be comprehensive
|
||||
7. The library MUST be usable in a production environment
|
||||
8. EVERY HTTP framework integration MUST be fully implemented
|
||||
9. ALL storage adapters MUST be properly implemented
|
||||
10. Documentation MUST be complete with examples
|
||||
|
||||
Create this authentication library with the same comprehensive features and flexibility as the original codebase, ensuring it meets all the requirements outlined above.
|
||||
|
||||
## Additional Documents
|
||||
1. Detailed Project Design @docs/DESIGN.md
|
||||
2. Project Plan @docs/PROJECT_PLAN.md
|
415
docs/DESIGN.md
Normal file
415
docs/DESIGN.md
Normal file
|
@ -0,0 +1,415 @@
|
|||
# Auth2 Library Design Document
|
||||
|
||||
## Overview
|
||||
|
||||
Auth2 is a comprehensive, modular authentication library for Go applications. It provides a complete solution for user authentication, authorization, session management, and user data handling with a focus on security, flexibility, and ease of integration.
|
||||
|
||||
## Core Architecture
|
||||
|
||||
The library follows hexagonal/clean architecture principles to maintain separation of concerns and enable high modularity:
|
||||
|
||||
1. **Domain Layer**: Core entities and business rules
|
||||
2. **Application Layer**: Use cases and application logic
|
||||
3. **Infrastructure Layer**: External systems integration and adapters
|
||||
4. **Interface Layer**: HTTP and framework integration
|
||||
|
||||
### Key Design Principles
|
||||
|
||||
1. Interface-driven design for all components
|
||||
2. Dependency injection for loose coupling
|
||||
3. Clear error handling with descriptive errors
|
||||
4. Comprehensive logging with context
|
||||
5. Thread-safe implementations
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
github.com/Fishwaldo/auth2/
|
||||
├── pkg/
|
||||
│ ├── auth/ # Core authentication logic
|
||||
│ │ ├── providers/ # Authentication providers
|
||||
│ │ │ ├── basic/ # Username/password
|
||||
│ │ │ ├── oauth/ # OAuth providers
|
||||
│ │ │ │ ├── google/ # Google OAuth
|
||||
│ │ │ │ ├── github/ # GitHub OAuth
|
||||
│ │ │ │ ├── microsoft/ # Microsoft OAuth
|
||||
│ │ │ │ └── facebook/ # Facebook OAuth
|
||||
│ │ │ └── saml/ # SAML implementation
|
||||
│ │ ├── mfa/ # MFA implementations
|
||||
│ │ │ ├── totp/ # Time-based OTP
|
||||
│ │ │ ├── webauthn/ # WebAuthn/FIDO2
|
||||
│ │ │ ├── email/ # Email OTP
|
||||
│ │ │ └── backupcodes/ # Backup codes
|
||||
│ │ └── verification/ # Account verification
|
||||
│ │ ├── email/ # Email verification
|
||||
│ │ └── providers/ # Email providers
|
||||
│ ├── session/ # Session management
|
||||
│ │ ├── cookie/ # Cookie-based sessions
|
||||
│ │ ├── jwt/ # JWT token authentication
|
||||
│ │ └── token/ # Bearer token authentication
|
||||
│ ├── user/ # User management
|
||||
│ │ ├── profile/ # User profile handling
|
||||
│ │ └── password/ # Password management
|
||||
│ ├── rbac/ # Role-based access control
|
||||
│ │ ├── role/ # Role management
|
||||
│ │ ├── permission/ # Permission management
|
||||
│ │ └── group/ # Group management
|
||||
│ ├── storage/ # Storage interfaces
|
||||
│ │ ├── sql/ # Standard SQL implementation
|
||||
│ │ ├── gorm/ # GORM implementation
|
||||
│ │ ├── ent/ # Ent implementation
|
||||
│ │ └── memory/ # In-memory implementation
|
||||
│ ├── cache/ # Cache interfaces and implementations
|
||||
│ │ └── redis/ # Redis cache implementation
|
||||
│ ├── http/ # HTTP framework adapters
|
||||
│ │ ├── std/ # Standard library
|
||||
│ │ ├── chi/ # Chi router
|
||||
│ │ ├── echo/ # Echo framework
|
||||
│ │ ├── fiber/ # Fiber
|
||||
│ │ ├── gin/ # Gin
|
||||
│ │ ├── gorilla/ # Gorilla mux
|
||||
│ │ ├── httprouter/ # httprouter
|
||||
│ │ ├── huma/ # Huma
|
||||
│ │ └── fasthttp/ # FastHTTP
|
||||
│ ├── security/ # Security features
|
||||
│ │ ├── ratelimit/ # Rate limiting
|
||||
│ │ ├── csrf/ # CSRF protection
|
||||
│ │ ├── bruteforce/ # Brute force protection
|
||||
│ │ └── recovery/ # Account recovery
|
||||
│ ├── config/ # Configuration
|
||||
│ └── log/ # Logging utilities
|
||||
├── internal/ # Internal implementation details
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── errors/ # Error definitions
|
||||
├── test/ # Integration tests
|
||||
│ └── mocks/ # Mock implementations
|
||||
└── examples/ # Example integrations
|
||||
├── basic/ # Simple integration
|
||||
├── complete/ # Complete implementation
|
||||
└── custom/ # Custom provider implementation
|
||||
```
|
||||
|
||||
## Core Components Design
|
||||
|
||||
### Authentication Provider Interface
|
||||
|
||||
```go
|
||||
type AuthProvider interface {
|
||||
// Authenticate verifies user credentials and returns a user ID if successful
|
||||
Authenticate(ctx context.Context, credentials interface{}) (string, error)
|
||||
|
||||
// Supports returns true if this provider supports the given credentials type
|
||||
Supports(credentials interface{}) bool
|
||||
|
||||
// GetID returns the unique identifier for this provider
|
||||
GetID() string
|
||||
|
||||
// Initialize sets up the provider with necessary configuration
|
||||
Initialize(config interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
### MFA Provider Interface
|
||||
|
||||
```go
|
||||
type MFAProvider interface {
|
||||
// Setup initializes the MFA method for a user
|
||||
Setup(ctx context.Context, userID string) (SetupData, error)
|
||||
|
||||
// Verify checks if the provided code is valid
|
||||
Verify(ctx context.Context, userID string, code string) (bool, error)
|
||||
|
||||
// GetID returns the unique identifier for this MFA provider
|
||||
GetID() string
|
||||
|
||||
// Initialize sets up the provider with necessary configuration
|
||||
Initialize(config interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
### Dual-Mode Providers
|
||||
|
||||
Some authentication methods like FIDO2/WebAuthn can function both as primary authentication and as MFA methods. These providers implement both interfaces:
|
||||
|
||||
```go
|
||||
// DualModeProvider implements both AuthProvider and MFAProvider interfaces
|
||||
type DualModeProvider interface {
|
||||
AuthProvider
|
||||
MFAProvider
|
||||
}
|
||||
```
|
||||
|
||||
This design allows WebAuthn/FIDO2 to be used either as a standalone authentication method (passwordless) or as a second factor alongside another authentication method.
|
||||
|
||||
### Storage Interface
|
||||
|
||||
```go
|
||||
type UserStore interface {
|
||||
// CreateUser creates a new user
|
||||
CreateUser(ctx context.Context, user User) (string, error)
|
||||
|
||||
// GetUser retrieves a user by ID
|
||||
GetUser(ctx context.Context, userID string) (User, error)
|
||||
|
||||
// GetUserByUsername retrieves a user by username
|
||||
GetUserByUsername(ctx context.Context, username string) (User, error)
|
||||
|
||||
// UpdateUser updates an existing user
|
||||
UpdateUser(ctx context.Context, user User) error
|
||||
|
||||
// DeleteUser deletes a user
|
||||
DeleteUser(ctx context.Context, userID string) error
|
||||
|
||||
// GetUserProfile gets a user's profile data
|
||||
GetUserProfile(ctx context.Context, userID string) (map[string]interface{}, error)
|
||||
|
||||
// UpdateUserProfile updates a user's profile data
|
||||
UpdateUserProfile(ctx context.Context, userID string, profile map[string]interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
### Session Management Interface
|
||||
|
||||
```go
|
||||
type SessionManager interface {
|
||||
// CreateSession creates a new session for a user
|
||||
CreateSession(ctx context.Context, userID string, data map[string]interface{}) (Session, error)
|
||||
|
||||
// GetSession retrieves a session by ID
|
||||
GetSession(ctx context.Context, sessionID string) (Session, error)
|
||||
|
||||
// RefreshSession extends the session lifetime
|
||||
RefreshSession(ctx context.Context, sessionID string) error
|
||||
|
||||
// RevokeSession invalidates a session
|
||||
RevokeSession(ctx context.Context, sessionID string) error
|
||||
|
||||
// RevokeAllUserSessions invalidates all sessions for a user
|
||||
RevokeAllUserSessions(ctx context.Context, userID string) error
|
||||
}
|
||||
```
|
||||
|
||||
### RBAC Interface
|
||||
|
||||
```go
|
||||
type RBACManager interface {
|
||||
// CreateRole creates a new role
|
||||
CreateRole(ctx context.Context, role Role) (string, error)
|
||||
|
||||
// GetRole retrieves a role by ID
|
||||
GetRole(ctx context.Context, roleID string) (Role, error)
|
||||
|
||||
// UpdateRole updates an existing role
|
||||
UpdateRole(ctx context.Context, role Role) error
|
||||
|
||||
// DeleteRole deletes a role
|
||||
DeleteRole(ctx context.Context, roleID string) error
|
||||
|
||||
// AssignRoleToUser assigns a role to a user
|
||||
AssignRoleToUser(ctx context.Context, userID, roleID string) error
|
||||
|
||||
// RevokeRoleFromUser revokes a role from a user
|
||||
RevokeRoleFromUser(ctx context.Context, userID, roleID string) error
|
||||
|
||||
// HasPermission checks if a user has a specific permission
|
||||
HasPermission(ctx context.Context, userID, permission string) (bool, error)
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Framework Integration
|
||||
|
||||
Each HTTP framework adapter will implement the following interface:
|
||||
|
||||
```go
|
||||
type HTTPAdapter interface {
|
||||
// Middleware returns middleware for the specific framework
|
||||
Middleware() interface{}
|
||||
|
||||
// RegisterRoutes registers authentication routes
|
||||
RegisterRoutes() error
|
||||
|
||||
// ParseRequest extracts authentication data from requests
|
||||
ParseRequest(request interface{}) (AuthData, error)
|
||||
|
||||
// WriteResponse writes authentication responses
|
||||
WriteResponse(response interface{}, data interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
## Security Features Implementation
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```go
|
||||
type RateLimiter interface {
|
||||
// Allow checks if the operation is allowed based on the key
|
||||
Allow(ctx context.Context, key string) (bool, error)
|
||||
|
||||
// Reset resets the counter for a key
|
||||
Reset(ctx context.Context, key string) error
|
||||
}
|
||||
```
|
||||
|
||||
### CSRF Protection
|
||||
|
||||
```go
|
||||
type CSRFProtector interface {
|
||||
// GenerateToken generates a new CSRF token
|
||||
GenerateToken(ctx context.Context, userID string) (string, error)
|
||||
|
||||
// ValidateToken validates a CSRF token
|
||||
ValidateToken(ctx context.Context, userID, token string) (bool, error)
|
||||
}
|
||||
```
|
||||
|
||||
## Main Package API
|
||||
|
||||
```go
|
||||
// Auth2 is the main entry point for the library
|
||||
type Auth2 struct {
|
||||
// Configuration
|
||||
Config *Config
|
||||
|
||||
// User management
|
||||
UserManager *user.Manager
|
||||
|
||||
// Authentication
|
||||
AuthManager *auth.Manager
|
||||
|
||||
// Session management
|
||||
SessionManager session.Manager
|
||||
|
||||
// RBAC
|
||||
RBACManager rbac.Manager
|
||||
|
||||
// Security features
|
||||
Security *security.Manager
|
||||
}
|
||||
|
||||
// New creates a new Auth2 instance with the provided configuration
|
||||
func New(config *Config) (*Auth2, error) {
|
||||
// Initialize all components
|
||||
// ...
|
||||
}
|
||||
|
||||
// RegisterHTTPAdapter registers an HTTP framework adapter
|
||||
func (a *Auth2) RegisterHTTPAdapter(adapter http.Adapter) error {
|
||||
// Register adapter
|
||||
// ...
|
||||
}
|
||||
|
||||
// RegisterAuthProvider registers an authentication provider
|
||||
func (a *Auth2) RegisterAuthProvider(provider auth.Provider) error {
|
||||
// Register provider
|
||||
// ...
|
||||
}
|
||||
|
||||
// RegisterMFAProvider registers an MFA provider
|
||||
func (a *Auth2) RegisterMFAProvider(provider auth.MFAProvider) error {
|
||||
// Register provider
|
||||
// ...
|
||||
}
|
||||
|
||||
// RegisterStorageAdapter registers a storage adapter
|
||||
func (a *Auth2) RegisterStorageAdapter(adapter storage.Adapter) error {
|
||||
// Register adapter
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All errors will be defined in a central location:
|
||||
|
||||
```go
|
||||
package errors
|
||||
|
||||
// Common errors
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
ErrUserDisabled = errors.New("user account is disabled")
|
||||
ErrSessionExpired = errors.New("session has expired")
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrPermissionDenied = errors.New("permission denied")
|
||||
ErrRateLimitExceeded = errors.New("rate limit exceeded")
|
||||
ErrInvalidMFACode = errors.New("invalid MFA code")
|
||||
// ...
|
||||
)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
// Config holds the configuration for the entire library
|
||||
type Config struct {
|
||||
// General settings
|
||||
AppName string
|
||||
Environment string
|
||||
|
||||
// Auth settings
|
||||
Auth struct {
|
||||
PasswordPolicy *password.Policy
|
||||
SessionDuration time.Duration
|
||||
RequireEmailVerification bool
|
||||
MaxLoginAttempts int
|
||||
LockoutDuration time.Duration
|
||||
}
|
||||
|
||||
// Storage settings
|
||||
Storage struct {
|
||||
Type string // sql, gorm, ent, memory
|
||||
ConnectionString string
|
||||
}
|
||||
|
||||
// Security settings
|
||||
Security struct {
|
||||
CSRFTokenExpiry time.Duration
|
||||
SecureCookies bool
|
||||
SameSite string
|
||||
}
|
||||
|
||||
// Logging
|
||||
Logger slog.Logger
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Black Box Unit Testing**:
|
||||
- Test all components through their public interfaces only
|
||||
- Do not test internal implementation details
|
||||
- Use table-driven tests for comprehensive coverage
|
||||
- Mock all external dependencies
|
||||
- Each component must be testable in isolation
|
||||
- Test both success and failure scenarios
|
||||
|
||||
2. **Integration Testing**:
|
||||
- Test integration with all supported storage adapters
|
||||
- Test integration with all supported HTTP frameworks
|
||||
- Use in-memory implementations where appropriate
|
||||
- Focus on contract adherence between components
|
||||
|
||||
3. **End-to-End Testing**:
|
||||
- Test complete authentication flows
|
||||
- Test security features effectiveness
|
||||
- Test all public APIs
|
||||
- Verify proper error handling and error messages
|
||||
|
||||
## Documentation
|
||||
|
||||
1. **Package Documentation**:
|
||||
- Godoc-compliant documentation for all exported functions and types
|
||||
- Usage examples for all major components
|
||||
|
||||
2. **Integration Guides**:
|
||||
- Step-by-step guides for each supported HTTP framework
|
||||
- Configuration examples for different deployment scenarios
|
||||
|
||||
3. **API Reference**:
|
||||
- Complete API reference with examples
|
||||
- OpenAPI specification for REST APIs
|
||||
|
||||
4. **Security Recommendations**:
|
||||
- Best practices for secure configuration
|
||||
- Security considerations for production deployment
|
315
docs/PROJECT_PLAN.md
Normal file
315
docs/PROJECT_PLAN.md
Normal file
|
@ -0,0 +1,315 @@
|
|||
# Auth2 Library Implementation Plan
|
||||
|
||||
This document outlines the step-by-step implementation plan for the Auth2 library, a comprehensive, production-ready authentication solution for Go applications.
|
||||
|
||||
## Phase 1: Foundation & Core Architecture
|
||||
|
||||
### 1.1 Project Setup
|
||||
- [x] Initialize Go module and directory structure
|
||||
- [x] Set up logging with log/slog
|
||||
- [x] Create error types and handling mechanisms
|
||||
- [x] Implement configuration structures
|
||||
|
||||
### 1.2 Plugin System Architecture
|
||||
- [ ] Design plugin registry interface
|
||||
- [ ] Implement plugin loading mechanism
|
||||
- [ ] Create provider discovery system
|
||||
- [ ] Build versioning and compatibility checking
|
||||
|
||||
### 1.3 Core Domain Models
|
||||
- [ ] Define User model and interfaces
|
||||
- [ ] Create Context wrapper for auth context
|
||||
- [ ] Implement base interfaces for all components
|
||||
- [ ] Build error handling patterns
|
||||
|
||||
## Phase 2: Core Authentication Framework
|
||||
|
||||
### 2.1 Authentication Provider Interface
|
||||
- [ ] Define AuthProvider interface
|
||||
- [ ] Create ProviderManager for managing multiple providers
|
||||
- [ ] Implement provider registration system
|
||||
- [ ] Build chain-of-responsibility pattern for auth attempts
|
||||
|
||||
### 2.2 Basic Authentication
|
||||
- [ ] Implement username/password provider
|
||||
- [ ] Create password hashing utilities (bcrypt, argon2id)
|
||||
- [ ] Build password policy enforcement
|
||||
- [ ] Implement account locking mechanism
|
||||
|
||||
### 2.3 WebAuthn/FIDO2 as Primary Authentication
|
||||
- [ ] Implement WebAuthn passwordless registration
|
||||
- [ ] Create WebAuthn passwordless authentication
|
||||
- [ ] Build attestation verification
|
||||
- [ ] Implement credential storage and management
|
||||
- [ ] Create dual-mode provider interface for both primary and MFA use
|
||||
|
||||
### 2.4 OAuth2 Framework
|
||||
- [ ] Design generic OAuth2 provider
|
||||
- [ ] Implement OAuth2 flow handlers
|
||||
- [ ] Create token storage and validation
|
||||
- [ ] Build user profile mapping utilities
|
||||
|
||||
### 2.5 OAuth2 Providers
|
||||
- [ ] Implement Google OAuth2 provider
|
||||
- [ ] Implement GitHub OAuth2 provider
|
||||
- [ ] Implement Microsoft OAuth2 provider
|
||||
- [ ] Implement Facebook OAuth2 provider
|
||||
|
||||
### 2.6 SAML Authentication
|
||||
- [ ] Implement SAML provider interface
|
||||
- [ ] Create SAML assertion parser
|
||||
- [ ] Build SAML request/response handlers
|
||||
- [ ] Implement metadata handlers
|
||||
|
||||
## Phase 3: Multi-Factor Authentication
|
||||
|
||||
### 3.1 MFA Framework
|
||||
- [ ] Define MFA provider interface
|
||||
- [ ] Create MFA registration flow
|
||||
- [ ] Implement MFA verification flow
|
||||
- [ ] Build MFA fallback mechanisms
|
||||
|
||||
### 3.2 TOTP Implementation
|
||||
- [ ] Implement TOTP algorithm (RFC 6238)
|
||||
- [ ] Create QR code generation for setup
|
||||
- [ ] Build key storage and management
|
||||
- [ ] Implement validation with drift windows
|
||||
|
||||
### 3.3 WebAuthn/FIDO2 as MFA
|
||||
- [ ] Implement WebAuthn MFA registration
|
||||
- [ ] Create WebAuthn MFA verification
|
||||
- [ ] Build integration with primary authentication methods
|
||||
- [ ] Implement fallback mechanisms
|
||||
|
||||
### 3.4 Email OTP
|
||||
- [ ] Create email OTP generation
|
||||
- [ ] Implement OTP storage and validation
|
||||
- [ ] Build email delivery interface
|
||||
- [ ] Create rate limiting for OTP requests
|
||||
|
||||
### 3.5 Backup Codes
|
||||
- [ ] Implement secure backup code generation
|
||||
- [ ] Create storage and validation
|
||||
- [ ] Build code regeneration mechanism
|
||||
- [ ] Implement usage tracking
|
||||
|
||||
## Phase 4: Session Management
|
||||
|
||||
### 4.1 Session Framework
|
||||
- [ ] Define Session interface
|
||||
- [ ] Create SessionManager interface
|
||||
- [ ] Implement session creation/validation flow
|
||||
- [ ] Build session store interface
|
||||
|
||||
### 4.2 Cookie Sessions
|
||||
- [ ] Implement secure cookie creation
|
||||
- [ ] Create cookie signing and encryption
|
||||
- [ ] Build cookie session validation
|
||||
- [ ] Implement cookie refresh mechanism
|
||||
|
||||
### 4.3 JWT Sessions
|
||||
- [ ] Implement JWT generation and validation
|
||||
- [ ] Create claims mapping system
|
||||
- [ ] Build key rotation mechanism
|
||||
- [ ] Implement token blacklisting for revocation
|
||||
|
||||
### 4.4 Bearer Token Management
|
||||
- [ ] Create token generation and validation
|
||||
- [ ] Implement token refresh mechanism
|
||||
- [ ] Build token revocation system
|
||||
- [ ] Create token metadata storage
|
||||
|
||||
## Phase 5: RBAC Implementation
|
||||
|
||||
### 5.1 RBAC Core
|
||||
- [ ] Define Role and Permission models
|
||||
- [ ] Create RBACManager interface
|
||||
- [ ] Implement permission checking
|
||||
- [ ] Build role assignment mechanisms
|
||||
|
||||
### 5.2 Permission Management
|
||||
- [ ] Implement permission creation and management
|
||||
- [ ] Create permission inheritance system
|
||||
- [ ] Build permission checking optimization
|
||||
- [ ] Implement permission caching
|
||||
|
||||
### 5.3 Role Management
|
||||
- [ ] Create role hierarchy system
|
||||
- [ ] Implement role assignment to users
|
||||
- [ ] Build role relationship management
|
||||
- [ ] Create role-based permission resolution
|
||||
|
||||
### 5.4 Group Management
|
||||
- [ ] Implement group creation and management
|
||||
- [ ] Create user group assignment
|
||||
- [ ] Build group-role relationships
|
||||
- [ ] Implement group-based permission resolution
|
||||
|
||||
## Phase 6: Storage Adapters
|
||||
|
||||
### 6.1 Storage Interface
|
||||
- [ ] Define comprehensive storage interfaces
|
||||
- [ ] Create adapter registration system
|
||||
- [ ] Implement transaction support
|
||||
- [ ] Build query interface
|
||||
|
||||
### 6.2 In-Memory Storage
|
||||
- [ ] Implement in-memory user storage
|
||||
- [ ] Create in-memory session storage
|
||||
- [ ] Build in-memory RBAC storage
|
||||
- [ ] Implement test utilities
|
||||
|
||||
### 6.3 SQL Adapter
|
||||
- [ ] Create standard SQL implementation
|
||||
- [ ] Implement SQL schema management
|
||||
- [ ] Build query optimization
|
||||
- [ ] Create connection pooling
|
||||
|
||||
### 6.4 GORM Adapter
|
||||
- [ ] Implement GORM models
|
||||
- [ ] Create GORM repository implementations
|
||||
- [ ] Build efficient query patterns
|
||||
- [ ] Implement migration utilities
|
||||
|
||||
### 6.5 Ent Adapter
|
||||
- [ ] Create Ent schema definitions
|
||||
- [ ] Implement Ent client wrappers
|
||||
- [ ] Build repository implementations
|
||||
- [ ] Create efficient query builders
|
||||
|
||||
## Phase 7: HTTP Framework Integration
|
||||
|
||||
### 7.1 HTTP Framework Interface
|
||||
- [ ] Define middleware interface
|
||||
- [ ] Create request parser interface
|
||||
- [ ] Implement response writer interface
|
||||
- [ ] Build route registration system
|
||||
|
||||
### 7.2 Standard Library Integration
|
||||
- [ ] Implement net/http middleware
|
||||
- [ ] Create request handlers
|
||||
- [ ] Build response utilities
|
||||
- [ ] Implement session management
|
||||
|
||||
### 7.3 Framework-Specific Adapters
|
||||
- [ ] Implement Chi integration
|
||||
- [ ] Create Echo integration
|
||||
- [ ] Build Fiber integration
|
||||
- [ ] Implement Gin integration
|
||||
- [ ] Create Gorilla Mux integration
|
||||
- [ ] Build httprouter integration
|
||||
- [ ] Implement Huma integration
|
||||
- [ ] Create FastHTTP integration
|
||||
|
||||
## Phase 8: Security Features
|
||||
|
||||
### 8.1 Rate Limiting
|
||||
- [ ] Implement rate limiter interface
|
||||
- [ ] Create in-memory rate limiter
|
||||
- [ ] Build distributed rate limiter
|
||||
- [ ] Implement rate limit middleware
|
||||
|
||||
### 8.2 Brute Force Protection
|
||||
- [ ] Create failed attempt tracking
|
||||
- [ ] Implement progressive backoff
|
||||
- [ ] Build account locking mechanism
|
||||
- [ ] Create notification system
|
||||
|
||||
### 8.3 CSRF Protection
|
||||
- [ ] Implement token generation and validation
|
||||
- [ ] Create CSRF middleware
|
||||
- [ ] Build token storage
|
||||
- [ ] Implement SameSite cookie protection
|
||||
|
||||
### 8.4 Password Security
|
||||
- [ ] Create password strength validation
|
||||
- [ ] Implement password history
|
||||
- [ ] Build password rotation policies
|
||||
- [ ] Create secure password reset flow
|
||||
|
||||
### 8.5 Account Recovery
|
||||
- [ ] Implement secure recovery flow
|
||||
- [ ] Create recovery token management
|
||||
- [ ] Build multi-channel verification
|
||||
- [ ] Implement account recovery audit
|
||||
|
||||
## Phase 9: User Management
|
||||
|
||||
### 9.1 Registration Flow
|
||||
- [ ] Implement user registration
|
||||
- [ ] Create email verification
|
||||
- [ ] Build user activation flow
|
||||
- [ ] Implement profile creation
|
||||
|
||||
### 9.2 Profile Management
|
||||
- [ ] Create profile update functionality
|
||||
- [ ] Implement data validation
|
||||
- [ ] Build custom field support
|
||||
- [ ] Create profile data encryption
|
||||
|
||||
### 9.3 Account Management
|
||||
- [ ] Implement account locking/unlocking
|
||||
- [ ] Create password reset flow
|
||||
- [ ] Build account deletion
|
||||
- [ ] Implement account merging
|
||||
|
||||
## Phase 10: Testing & Documentation
|
||||
|
||||
### 10.1 Unit Testing
|
||||
- [ ] Create comprehensive test suite for core components
|
||||
- [ ] Implement mock providers
|
||||
- [ ] Build test utilities
|
||||
- [ ] Create test coverage reports
|
||||
|
||||
### 10.2 Integration Testing
|
||||
- [ ] Implement end-to-end authentication flow tests
|
||||
- [ ] Create storage adapter tests
|
||||
- [ ] Build HTTP integration tests
|
||||
- [ ] Implement security feature tests
|
||||
|
||||
### 10.3 Documentation
|
||||
- [ ] Create comprehensive API documentation
|
||||
- [ ] Build usage examples
|
||||
- [ ] Implement godoc-compatible documentation
|
||||
- [ ] Create security best practices guide
|
||||
|
||||
### 10.4 Example Applications
|
||||
- [ ] Build basic authentication example
|
||||
- [ ] Create complete feature showcase
|
||||
- [ ] Implement custom provider example
|
||||
- [ ] Build framework integration examples
|
||||
|
||||
## Deliverables Timeline
|
||||
|
||||
### Milestone 1: Core Framework (Weeks 1-2)
|
||||
- [x] Project setup complete
|
||||
- [ ] Plugin system architecture implemented
|
||||
- [ ] Core domain models defined
|
||||
- [ ] Basic authentication working
|
||||
|
||||
### Milestone 2: Authentication Providers (Weeks 3-4)
|
||||
- [ ] OAuth2 framework implemented
|
||||
- [ ] All OAuth2 providers working
|
||||
- [ ] SAML authentication working
|
||||
- [ ] WebAuthn passwordless authentication working
|
||||
- [ ] Session management framework complete
|
||||
|
||||
### Milestone 3: MFA & Security (Weeks 5-6)
|
||||
- [ ] All MFA methods implemented
|
||||
- [ ] Security features working
|
||||
- [ ] RBAC implementation complete
|
||||
- [ ] User management flows working
|
||||
|
||||
### Milestone 4: Storage & HTTP Integration (Weeks 7-8)
|
||||
- [ ] All storage adapters implemented
|
||||
- [ ] HTTP framework integration complete
|
||||
- [ ] Integration tests passing
|
||||
- [ ] Documentation complete
|
||||
|
||||
## Quality Assurance Approach
|
||||
|
||||
### Code Quality
|
||||
- [ ] All code must pass linting and formatting checks
|
||||
- [ ] Test coverage must exceed 80%
|
||||
- [ ] No exported function, type, or variable without documentation
|
||||
- [ ] No known security vulnerabilities
|
72
docs/README.md
Normal file
72
docs/README.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Auth2 Documentation
|
||||
|
||||
Welcome to the Auth2 documentation. This guide will help you understand how to use and extend the Auth2 authentication library.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Getting Started](./getting-started.md)
|
||||
- Installation
|
||||
- Basic Setup
|
||||
- Configuration Options
|
||||
|
||||
2. [Core Concepts](./core-concepts.md)
|
||||
- Architecture Overview
|
||||
- Authentication Flow
|
||||
- Session Management
|
||||
|
||||
3. [Authentication Methods](./auth-methods/README.md)
|
||||
- [Username/Password](./auth-methods/basic.md)
|
||||
- [OAuth2](./auth-methods/oauth2.md)
|
||||
- [SAML](./auth-methods/saml.md)
|
||||
|
||||
4. [Multi-Factor Authentication](./mfa/README.md)
|
||||
- [TOTP](./mfa/totp.md)
|
||||
- [WebAuthn/FIDO2](./mfa/webauthn.md)
|
||||
- [Email OTP](./mfa/email-otp.md)
|
||||
- [Backup Codes](./mfa/backup-codes.md)
|
||||
|
||||
5. [Storage Adapters](./storage/README.md)
|
||||
- [Memory](./storage/memory.md)
|
||||
- [SQL](./storage/sql.md)
|
||||
- [GORM](./storage/gorm.md)
|
||||
- [Ent ORM](./storage/ent.md)
|
||||
|
||||
6. [HTTP Framework Integration](./http/README.md)
|
||||
- [Standard Library](./http/std.md)
|
||||
- [Chi](./http/chi.md)
|
||||
- [Echo](./http/echo.md)
|
||||
- [Fiber](./http/fiber.md)
|
||||
- [Gin](./http/gin.md)
|
||||
- [More Frameworks](./http/other.md)
|
||||
|
||||
7. [Role-Based Access Control](./rbac/README.md)
|
||||
- [Roles and Permissions](./rbac/roles.md)
|
||||
- [User-Role Assignment](./rbac/assignment.md)
|
||||
- [Permission Checking](./rbac/checking.md)
|
||||
|
||||
8. [Security Features](./security/README.md)
|
||||
- [Brute Force Protection](./security/brute-force.md)
|
||||
- [Rate Limiting](./security/rate-limiting.md)
|
||||
- [CSRF Protection](./security/csrf.md)
|
||||
- [Account Recovery](./security/account-recovery.md)
|
||||
|
||||
9. [Examples](./examples/README.md)
|
||||
- [Basic Authentication](./examples/basic-auth.md)
|
||||
- [OAuth Integration](./examples/oauth.md)
|
||||
- [Multi-Factor Authentication](./examples/mfa.md)
|
||||
- [Custom Authentication Provider](./examples/custom-provider.md)
|
||||
|
||||
10. [API Reference](./api/README.md)
|
||||
- [Public API](./api/public.md)
|
||||
- [Interfaces](./api/interfaces.md)
|
||||
- [Error Types](./api/errors.md)
|
||||
|
||||
11. [Advanced Usage](./advanced/README.md)
|
||||
- [Custom Authentication Providers](./advanced/custom-auth.md)
|
||||
- [Custom Storage Adapters](./advanced/custom-storage.md)
|
||||
- [Extending the Core](./advanced/extending.md)
|
||||
|
||||
12. [Contributing](./contributing.md)
|
||||
- Development Guidelines
|
||||
- Testing
|
||||
- Pull Request Process
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module github.com/Fishwaldo/auth2
|
||||
|
||||
go 1.24
|
251
internal/errors/errors.go
Normal file
251
internal/errors/errors.go
Normal file
|
@ -0,0 +1,251 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Standard errors in the auth2 package
|
||||
var (
|
||||
// General errors
|
||||
ErrInternal = errors.New("internal error")
|
||||
ErrNotImplemented = errors.New("not implemented")
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrInvalidOperation = errors.New("invalid operation")
|
||||
|
||||
// Authentication errors
|
||||
ErrAuthFailed = errors.New("authentication failed")
|
||||
ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrUserDisabled = errors.New("user account is disabled")
|
||||
ErrUserLocked = errors.New("user account is locked")
|
||||
|
||||
// MFA errors
|
||||
ErrMFARequired = errors.New("multi-factor authentication required")
|
||||
ErrMFAFailed = errors.New("multi-factor authentication failed")
|
||||
ErrMFANotEnabled = errors.New("multi-factor authentication not enabled")
|
||||
ErrInvalidMFACode = errors.New("invalid multi-factor authentication code")
|
||||
|
||||
// Session errors
|
||||
ErrSessionExpired = errors.New("session has expired")
|
||||
ErrInvalidSession = errors.New("invalid session")
|
||||
ErrSessionNotFound = errors.New("session not found")
|
||||
|
||||
// Token errors
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrTokenExpired = errors.New("token has expired")
|
||||
|
||||
// Permission errors
|
||||
ErrPermissionDenied = errors.New("permission denied")
|
||||
ErrRoleNotFound = errors.New("role not found")
|
||||
|
||||
// Rate limiting errors
|
||||
ErrRateLimitExceeded = errors.New("rate limit exceeded")
|
||||
|
||||
// Plugin errors
|
||||
ErrPluginNotFound = errors.New("plugin not found")
|
||||
ErrIncompatiblePlugin = errors.New("incompatible plugin")
|
||||
)
|
||||
|
||||
// AuthError represents an authentication-related error
|
||||
type AuthError struct {
|
||||
Err error
|
||||
Code string
|
||||
UserID string
|
||||
Reason string
|
||||
Temporary bool
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *AuthError) Error() string {
|
||||
if e.UserID != "" {
|
||||
return fmt.Sprintf("[%s] %s: %s (user: %s)", e.Code, e.Err.Error(), e.Reason, e.UserID)
|
||||
}
|
||||
return fmt.Sprintf("[%s] %s: %s", e.Code, e.Err.Error(), e.Reason)
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error
|
||||
func (e *AuthError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewAuthError creates a new AuthError
|
||||
func NewAuthError(err error, code string, reason string, userID string, temporary bool) *AuthError {
|
||||
return &AuthError{
|
||||
Err: err,
|
||||
Code: code,
|
||||
UserID: userID,
|
||||
Reason: reason,
|
||||
Temporary: temporary,
|
||||
}
|
||||
}
|
||||
|
||||
// IsAuthError checks if an error is an AuthError
|
||||
func IsAuthError(err error) bool {
|
||||
var authErr *AuthError
|
||||
return errors.As(err, &authErr)
|
||||
}
|
||||
|
||||
// GetAuthError extracts an AuthError from an error
|
||||
func GetAuthError(err error) (*AuthError, bool) {
|
||||
var authErr *AuthError
|
||||
if errors.As(err, &authErr) {
|
||||
return authErr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// PluginError represents an error related to plugins
|
||||
type PluginError struct {
|
||||
Err error
|
||||
PluginName string
|
||||
PluginType string
|
||||
Description string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *PluginError) Error() string {
|
||||
return fmt.Sprintf("plugin error [%s - %s]: %s: %s",
|
||||
e.PluginType, e.PluginName, e.Err.Error(), e.Description)
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error
|
||||
func (e *PluginError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewPluginError creates a new PluginError
|
||||
func NewPluginError(err error, pluginType, pluginName, description string) *PluginError {
|
||||
return &PluginError{
|
||||
Err: err,
|
||||
PluginType: pluginType,
|
||||
PluginName: pluginName,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
|
||||
// IsPluginError checks if an error is a PluginError
|
||||
func IsPluginError(err error) bool {
|
||||
var pluginErr *PluginError
|
||||
return errors.As(err, &pluginErr)
|
||||
}
|
||||
|
||||
// GetPluginError extracts a PluginError from an error
|
||||
func GetPluginError(err error) (*PluginError, bool) {
|
||||
var pluginErr *PluginError
|
||||
if errors.As(err, &pluginErr) {
|
||||
return pluginErr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ValidationError represents an error related to validation
|
||||
type ValidationError struct {
|
||||
Err error
|
||||
Field string
|
||||
Value interface{}
|
||||
Constraint string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("validation error [%s]: %s (got: %v, constraint: %s)",
|
||||
e.Field, e.Err.Error(), e.Value, e.Constraint)
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error
|
||||
func (e *ValidationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewValidationError creates a new ValidationError
|
||||
func NewValidationError(err error, field string, value interface{}, constraint string) *ValidationError {
|
||||
return &ValidationError{
|
||||
Err: err,
|
||||
Field: field,
|
||||
Value: value,
|
||||
Constraint: constraint,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidationError checks if an error is a ValidationError
|
||||
func IsValidationError(err error) bool {
|
||||
var validationErr *ValidationError
|
||||
return errors.As(err, &validationErr)
|
||||
}
|
||||
|
||||
// GetValidationError extracts a ValidationError from an error
|
||||
func GetValidationError(err error) (*ValidationError, bool) {
|
||||
var validationErr *ValidationError
|
||||
if errors.As(err, &validationErr) {
|
||||
return validationErr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// StorageError represents an error related to storage operations
|
||||
type StorageError struct {
|
||||
Err error
|
||||
Operation string
|
||||
Entity string
|
||||
StorageType string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *StorageError) Error() string {
|
||||
return fmt.Sprintf("storage error [%s - %s - %s]: %s",
|
||||
e.StorageType, e.Entity, e.Operation, e.Err.Error())
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error
|
||||
func (e *StorageError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewStorageError creates a new StorageError
|
||||
func NewStorageError(err error, storageType, entity, operation string) *StorageError {
|
||||
return &StorageError{
|
||||
Err: err,
|
||||
StorageType: storageType,
|
||||
Entity: entity,
|
||||
Operation: operation,
|
||||
}
|
||||
}
|
||||
|
||||
// IsStorageError checks if an error is a StorageError
|
||||
func IsStorageError(err error) bool {
|
||||
var storageErr *StorageError
|
||||
return errors.As(err, &storageErr)
|
||||
}
|
||||
|
||||
// GetStorageError extracts a StorageError from an error
|
||||
func GetStorageError(err error) (*StorageError, bool) {
|
||||
var storageErr *StorageError
|
||||
if errors.As(err, &storageErr) {
|
||||
return storageErr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Is checks if an error matches a target error
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// As finds the first error in err's chain that matches target
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// New creates a new error with the given message
|
||||
func New(text string) error {
|
||||
return errors.New(text)
|
||||
}
|
||||
|
||||
// Wrap wraps an error with additional context
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s: %w", message, err)
|
||||
}
|
172
pkg/auth2/auth2.go
Normal file
172
pkg/auth2/auth2.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package auth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/Fishwaldo/auth2/internal/errors"
|
||||
"github.com/Fishwaldo/auth2/pkg/config"
|
||||
"github.com/Fishwaldo/auth2/pkg/log"
|
||||
)
|
||||
|
||||
// Auth2 is the main entry point for the auth2 library
|
||||
type Auth2 struct {
|
||||
// Configuration
|
||||
config *config.Config
|
||||
|
||||
// Logger
|
||||
logger *log.Logger
|
||||
|
||||
// Registered plugins and providers
|
||||
providers map[string]interface{}
|
||||
|
||||
// Mutex for concurrent access
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a new Auth2 instance with the provided configuration
|
||||
func New(cfg *config.Config) (*Auth2, error) {
|
||||
if cfg == nil {
|
||||
cfg = config.DefaultConfig()
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure logging
|
||||
logger := cfg.ConfigureLogging()
|
||||
|
||||
a := &Auth2{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
providers: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Config returns the current configuration
|
||||
func (a *Auth2) Config() *config.Config {
|
||||
return a.config
|
||||
}
|
||||
|
||||
// Logger returns the logger
|
||||
func (a *Auth2) Logger() *log.Logger {
|
||||
return a.logger
|
||||
}
|
||||
|
||||
// GetProvider returns a registered provider by name and type
|
||||
func (a *Auth2) GetProvider(name string, providerType interface{}) (interface{}, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
key := providerTypeKey(name, providerType)
|
||||
if provider, ok := a.providers[key]; ok {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
return nil, errors.NewPluginError(
|
||||
errors.ErrPluginNotFound,
|
||||
getProviderTypeName(providerType),
|
||||
name,
|
||||
"provider not registered",
|
||||
)
|
||||
}
|
||||
|
||||
// RegisterProvider registers a new provider
|
||||
func (a *Auth2) RegisterProvider(name string, providerType interface{}, provider interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
key := providerTypeKey(name, providerType)
|
||||
|
||||
// Check if already registered
|
||||
if _, exists := a.providers[key]; exists {
|
||||
return errors.NewPluginError(
|
||||
errors.ErrInvalidOperation,
|
||||
getProviderTypeName(providerType),
|
||||
name,
|
||||
"provider already registered",
|
||||
)
|
||||
}
|
||||
|
||||
a.providers[key] = provider
|
||||
a.logger.Info("Registered provider", "name", name, "type", getProviderTypeName(providerType))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterProvider removes a registered provider
|
||||
func (a *Auth2) UnregisterProvider(name string, providerType interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
key := providerTypeKey(name, providerType)
|
||||
|
||||
if _, exists := a.providers[key]; !exists {
|
||||
return errors.NewPluginError(
|
||||
errors.ErrPluginNotFound,
|
||||
getProviderTypeName(providerType),
|
||||
name,
|
||||
"provider not registered",
|
||||
)
|
||||
}
|
||||
|
||||
delete(a.providers, key)
|
||||
a.logger.Info("Unregistered provider", "name", name, "type", getProviderTypeName(providerType))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// providerTypeKey generates a key for the providers map
|
||||
func providerTypeKey(name string, providerType interface{}) string {
|
||||
return name + ":" + getProviderTypeName(providerType)
|
||||
}
|
||||
|
||||
// getProviderTypeName returns a string representation of the provider type
|
||||
func getProviderTypeName(providerType interface{}) string {
|
||||
switch providerType.(type) {
|
||||
case nil:
|
||||
return "unknown"
|
||||
default:
|
||||
return fmt.Sprintf("%T", providerType)
|
||||
}
|
||||
}
|
||||
|
||||
// Context returns a new context with the auth2 instance
|
||||
func (a *Auth2) Context(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, auth2Key{}, a)
|
||||
}
|
||||
|
||||
// FromContext retrieves the auth2 instance from the context
|
||||
func FromContext(ctx context.Context) (*Auth2, bool) {
|
||||
if auth, ok := ctx.Value(auth2Key{}).(*Auth2); ok {
|
||||
return auth, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// auth2Key is used as the key for storing the auth2 instance in the context
|
||||
type auth2Key struct{}
|
||||
|
||||
// Initialize sets up the Auth2 instance
|
||||
func (a *Auth2) Initialize(ctx context.Context) error {
|
||||
// Initialize components based on configuration
|
||||
a.logger.Info("Initializing auth2", "version", Version)
|
||||
|
||||
// Additional initialization logic will be added as components are implemented
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown performs cleanup operations
|
||||
func (a *Auth2) Shutdown(ctx context.Context) error {
|
||||
a.logger.Info("Shutting down auth2")
|
||||
|
||||
// Cleanup logic will be added as components are implemented
|
||||
|
||||
return nil
|
||||
}
|
37
pkg/auth2/version.go
Normal file
37
pkg/auth2/version.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package auth2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Version information
|
||||
var (
|
||||
// Version is the current version of the auth2 library
|
||||
Version = "0.1.0"
|
||||
|
||||
// GitCommit is the git commit hash at build time
|
||||
GitCommit = "unknown"
|
||||
|
||||
// BuildDate is the date the binary was built
|
||||
BuildDate = "unknown"
|
||||
|
||||
// GoVersion is the Go version used to compile the binary
|
||||
GoVersion = runtime.Version()
|
||||
)
|
||||
|
||||
// VersionInfo returns a formatted string with version information
|
||||
func VersionInfo() string {
|
||||
return fmt.Sprintf(
|
||||
"auth2 version %s\nGit commit: %s\nBuilt on: %s\nGo version: %s",
|
||||
Version,
|
||||
GitCommit,
|
||||
BuildDate,
|
||||
GoVersion,
|
||||
)
|
||||
}
|
||||
|
||||
// GetVersion returns the current version
|
||||
func GetVersion() string {
|
||||
return Version
|
||||
}
|
404
pkg/config/config.go
Normal file
404
pkg/config/config.go
Normal file
|
@ -0,0 +1,404 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Fishwaldo/auth2/internal/errors"
|
||||
"github.com/Fishwaldo/auth2/pkg/log"
|
||||
)
|
||||
|
||||
// Config holds the main configuration for the auth2 library
|
||||
type Config struct {
|
||||
// General settings
|
||||
AppName string `json:"app_name"`
|
||||
Environment string `json:"environment"` // "development", "production", "testing"
|
||||
|
||||
// Logging configuration
|
||||
Logging *LoggingConfig `json:"logging"`
|
||||
|
||||
// Authentication settings
|
||||
Auth *AuthConfig `json:"auth"`
|
||||
|
||||
// Session management
|
||||
Session *SessionConfig `json:"session"`
|
||||
|
||||
// RBAC configuration
|
||||
RBAC *RBACConfig `json:"rbac"`
|
||||
|
||||
// Security settings
|
||||
Security *SecurityConfig `json:"security"`
|
||||
|
||||
// Storage configuration
|
||||
Storage *StorageConfig `json:"storage"`
|
||||
}
|
||||
|
||||
// LoggingConfig holds logging-specific configuration
|
||||
type LoggingConfig struct {
|
||||
Level string `json:"level"` // "debug", "info", "warn", "error"
|
||||
Format string `json:"format"` // "json", "text"
|
||||
AddSource bool `json:"add_source"`
|
||||
Fields []string `json:"fields"` // Additional fields to log from context
|
||||
}
|
||||
|
||||
// AuthConfig holds authentication-specific configuration
|
||||
type AuthConfig struct {
|
||||
PasswordPolicy *PasswordPolicy `json:"password_policy"`
|
||||
RequireEmailVerification bool `json:"require_email_verification"`
|
||||
MaxLoginAttempts int `json:"max_login_attempts"`
|
||||
LockoutDuration time.Duration `json:"lockout_duration"`
|
||||
MFAEnabled bool `json:"mfa_enabled"`
|
||||
DefaultMFAType string `json:"default_mfa_type"` // "totp", "webauthn", "email", "backup"
|
||||
VerificationExpiry time.Duration `json:"verification_expiry"`
|
||||
}
|
||||
|
||||
// PasswordPolicy defines password requirements
|
||||
type PasswordPolicy struct {
|
||||
MinLength int `json:"min_length"`
|
||||
RequireUppercase bool `json:"require_uppercase"`
|
||||
RequireLowercase bool `json:"require_lowercase"`
|
||||
RequireDigits bool `json:"require_digits"`
|
||||
RequireSpecial bool `json:"require_special"`
|
||||
MaxRepeatedChars int `json:"max_repeated_chars"`
|
||||
PreventReuse bool `json:"prevent_reuse"`
|
||||
PreventReuseCount int `json:"prevent_reuse_count"`
|
||||
}
|
||||
|
||||
// SessionConfig holds session-specific configuration
|
||||
type SessionConfig struct {
|
||||
Type string `json:"type"` // "cookie", "jwt", "token"
|
||||
Duration time.Duration `json:"duration"`
|
||||
RefreshEnabled bool `json:"refresh_enabled"`
|
||||
RefreshDuration time.Duration `json:"refresh_duration"`
|
||||
Cookie *CookieConfig `json:"cookie"`
|
||||
JWT *JWTConfig `json:"jwt"`
|
||||
RedisEnabled bool `json:"redis_enabled"`
|
||||
RedisAddress string `json:"redis_address"`
|
||||
RedisPassword string `json:"redis_password"`
|
||||
RedisDB int `json:"redis_db"`
|
||||
DisableIPTracking bool `json:"disable_ip_tracking"`
|
||||
}
|
||||
|
||||
// CookieConfig holds cookie-specific configuration
|
||||
type CookieConfig struct {
|
||||
Name string `json:"name"`
|
||||
Domain string `json:"domain"`
|
||||
Path string `json:"path"`
|
||||
Secure bool `json:"secure"`
|
||||
HTTPOnly bool `json:"http_only"`
|
||||
SameSite string `json:"same_site"` // "strict", "lax", "none"
|
||||
Encryption bool `json:"encryption"`
|
||||
}
|
||||
|
||||
// JWTConfig holds JWT-specific configuration
|
||||
type JWTConfig struct {
|
||||
SigningMethod string `json:"signing_method"` // "HS256", "HS384", "HS512", "RS256", "RS384", "RS512"
|
||||
SigningKey string `json:"signing_key"`
|
||||
PublicKey string `json:"public_key"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
KeyFile string `json:"key_file"`
|
||||
KeyRotation bool `json:"key_rotation"`
|
||||
KeyRotationInterval time.Duration `json:"key_rotation_interval"`
|
||||
}
|
||||
|
||||
// RBACConfig holds RBAC-specific configuration
|
||||
type RBACConfig struct {
|
||||
EnableHierarchy bool `json:"enable_hierarchy"`
|
||||
EnableGroups bool `json:"enable_groups"`
|
||||
CacheEnabled bool `json:"cache_enabled"`
|
||||
CacheDuration time.Duration `json:"cache_duration"`
|
||||
DefaultRole string `json:"default_role"`
|
||||
SystemRoles []string `json:"system_roles"`
|
||||
DisablePermissions bool `json:"disable_permissions"`
|
||||
}
|
||||
|
||||
// SecurityConfig holds security-specific configuration
|
||||
type SecurityConfig struct {
|
||||
CSRFEnabled bool `json:"csrf_enabled"`
|
||||
CSRFTokenExpiry time.Duration `json:"csrf_token_expiry"`
|
||||
RateLimitEnabled bool `json:"rate_limit_enabled"`
|
||||
RateLimitRequests int `json:"rate_limit_requests"`
|
||||
RateLimitDuration time.Duration `json:"rate_limit_duration"`
|
||||
BruteForceEnabled bool `json:"brute_force_enabled"`
|
||||
BruteForceMaxAttempts int `json:"brute_force_max_attempts"`
|
||||
BruteForceWindow time.Duration `json:"brute_force_window"`
|
||||
BruteForceCooldown time.Duration `json:"brute_force_cooldown"`
|
||||
}
|
||||
|
||||
// StorageConfig holds storage-specific configuration
|
||||
type StorageConfig struct {
|
||||
Type string `json:"type"` // "memory", "sql", "gorm", "ent"
|
||||
ConnectionString string `json:"connection_string"`
|
||||
MaxConnections int `json:"max_connections"`
|
||||
ConnTimeout time.Duration `json:"conn_timeout"`
|
||||
QueryTimeout time.Duration `json:"query_timeout"`
|
||||
SQLDriver string `json:"sql_driver"` // "postgres", "mysql", "sqlite"
|
||||
MigrationsPath string `json:"migrations_path"`
|
||||
AutoMigrate bool `json:"auto_migrate"`
|
||||
}
|
||||
|
||||
// DefaultConfig returns a default configuration
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
AppName: "auth2",
|
||||
Environment: "development",
|
||||
Logging: &LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
AddSource: false,
|
||||
},
|
||||
Auth: &AuthConfig{
|
||||
PasswordPolicy: &PasswordPolicy{
|
||||
MinLength: 8,
|
||||
RequireUppercase: true,
|
||||
RequireLowercase: true,
|
||||
RequireDigits: true,
|
||||
RequireSpecial: true,
|
||||
MaxRepeatedChars: 3,
|
||||
PreventReuse: true,
|
||||
PreventReuseCount: 5,
|
||||
},
|
||||
RequireEmailVerification: true,
|
||||
MaxLoginAttempts: 5,
|
||||
LockoutDuration: 15 * time.Minute,
|
||||
MFAEnabled: false,
|
||||
DefaultMFAType: "totp",
|
||||
VerificationExpiry: 24 * time.Hour,
|
||||
},
|
||||
Session: &SessionConfig{
|
||||
Type: "cookie",
|
||||
Duration: 24 * time.Hour,
|
||||
RefreshEnabled: true,
|
||||
RefreshDuration: 7 * 24 * time.Hour,
|
||||
Cookie: &CookieConfig{
|
||||
Name: "auth2_session",
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
SameSite: "lax",
|
||||
Encryption: true,
|
||||
},
|
||||
JWT: &JWTConfig{
|
||||
SigningMethod: "HS256",
|
||||
KeyRotation: false,
|
||||
KeyRotationInterval: 24 * time.Hour,
|
||||
},
|
||||
DisableIPTracking: false,
|
||||
},
|
||||
RBAC: &RBACConfig{
|
||||
EnableHierarchy: true,
|
||||
EnableGroups: true,
|
||||
CacheEnabled: true,
|
||||
CacheDuration: 5 * time.Minute,
|
||||
DefaultRole: "user",
|
||||
SystemRoles: []string{"admin", "user", "guest"},
|
||||
},
|
||||
Security: &SecurityConfig{
|
||||
CSRFEnabled: true,
|
||||
CSRFTokenExpiry: 1 * time.Hour,
|
||||
RateLimitEnabled: true,
|
||||
RateLimitRequests: 100,
|
||||
RateLimitDuration: 1 * time.Minute,
|
||||
BruteForceEnabled: true,
|
||||
BruteForceMaxAttempts: 5,
|
||||
BruteForceWindow: 10 * time.Minute,
|
||||
BruteForceCooldown: 30 * time.Minute,
|
||||
},
|
||||
Storage: &StorageConfig{
|
||||
Type: "memory",
|
||||
MaxConnections: 10,
|
||||
ConnTimeout: 5 * time.Second,
|
||||
QueryTimeout: 10 * time.Second,
|
||||
AutoMigrate: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromFile loads configuration from a JSON file
|
||||
func LoadFromFile(path string) (*Config, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to open config file")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := DefaultConfig()
|
||||
if err := json.NewDecoder(file).Decode(config); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode config file")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Save writes the configuration to a file in JSON format
|
||||
func (c *Config) Save(path string) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create config file")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
encoder.SetIndent("", " ")
|
||||
if err := encoder.Encode(c); err != nil {
|
||||
return errors.Wrap(err, "failed to encode config")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if the configuration is valid
|
||||
func (c *Config) Validate() error {
|
||||
// Validate logging configuration
|
||||
if c.Logging != nil {
|
||||
level := c.Logging.Level
|
||||
if level != "debug" && level != "info" && level != "warn" && level != "error" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"logging.level",
|
||||
level,
|
||||
"must be one of: debug, info, warn, error",
|
||||
)
|
||||
}
|
||||
|
||||
format := c.Logging.Format
|
||||
if format != "json" && format != "text" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"logging.format",
|
||||
format,
|
||||
"must be one of: json, text",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate authentication configuration
|
||||
if c.Auth != nil {
|
||||
if c.Auth.PasswordPolicy != nil {
|
||||
if c.Auth.PasswordPolicy.MinLength < 6 {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"auth.password_policy.min_length",
|
||||
c.Auth.PasswordPolicy.MinLength,
|
||||
"must be at least 6",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Auth.MaxLoginAttempts < 1 {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"auth.max_login_attempts",
|
||||
c.Auth.MaxLoginAttempts,
|
||||
"must be at least 1",
|
||||
)
|
||||
}
|
||||
|
||||
if c.Auth.MFAEnabled {
|
||||
mfaType := c.Auth.DefaultMFAType
|
||||
if mfaType != "totp" && mfaType != "webauthn" && mfaType != "email" && mfaType != "backup" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"auth.default_mfa_type",
|
||||
mfaType,
|
||||
"must be one of: totp, webauthn, email, backup",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate session configuration
|
||||
if c.Session != nil {
|
||||
sessionType := c.Session.Type
|
||||
if sessionType != "cookie" && sessionType != "jwt" && sessionType != "token" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"session.type",
|
||||
sessionType,
|
||||
"must be one of: cookie, jwt, token",
|
||||
)
|
||||
}
|
||||
|
||||
if c.Session.Cookie != nil && c.Session.Type == "cookie" {
|
||||
sameSite := c.Session.Cookie.SameSite
|
||||
if sameSite != "strict" && sameSite != "lax" && sameSite != "none" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"session.cookie.same_site",
|
||||
sameSite,
|
||||
"must be one of: strict, lax, none",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Session.JWT != nil && c.Session.Type == "jwt" {
|
||||
method := c.Session.JWT.SigningMethod
|
||||
validMethods := map[string]bool{
|
||||
"HS256": true, "HS384": true, "HS512": true,
|
||||
"RS256": true, "RS384": true, "RS512": true,
|
||||
}
|
||||
if !validMethods[method] {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"session.jwt.signing_method",
|
||||
method,
|
||||
"must be one of: HS256, HS384, HS512, RS256, RS384, RS512",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate storage configuration
|
||||
if c.Storage != nil {
|
||||
storageType := c.Storage.Type
|
||||
if storageType != "memory" && storageType != "sql" && storageType != "gorm" && storageType != "ent" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"storage.type",
|
||||
storageType,
|
||||
"must be one of: memory, sql, gorm, ent",
|
||||
)
|
||||
}
|
||||
|
||||
if storageType == "sql" && c.Storage.SQLDriver == "" {
|
||||
return errors.NewValidationError(
|
||||
errors.ErrInvalidArgument,
|
||||
"storage.sql_driver",
|
||||
c.Storage.SQLDriver,
|
||||
"cannot be empty when storage.type is 'sql'",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureLogging sets up the logging system based on the configuration
|
||||
func (c *Config) ConfigureLogging() *log.Logger {
|
||||
var level log.Level
|
||||
switch c.Logging.Level {
|
||||
case "debug":
|
||||
level = log.LevelDebug
|
||||
case "info":
|
||||
level = log.LevelInfo
|
||||
case "warn":
|
||||
level = log.LevelWarn
|
||||
case "error":
|
||||
level = log.LevelError
|
||||
default:
|
||||
level = log.LevelInfo
|
||||
}
|
||||
|
||||
logConfig := &log.Config{
|
||||
Level: level,
|
||||
Format: c.Logging.Format,
|
||||
AddSource: c.Logging.AddSource,
|
||||
ContextKeys: c.Logging.Fields,
|
||||
}
|
||||
|
||||
logger := log.New(logConfig)
|
||||
log.SetDefault(logger)
|
||||
|
||||
return logger
|
||||
}
|
156
pkg/log/log.go
Normal file
156
pkg/log/log.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Level defines the logging level
|
||||
type Level = slog.Level
|
||||
|
||||
// Predefined logging levels
|
||||
const (
|
||||
LevelDebug = slog.LevelDebug
|
||||
LevelInfo = slog.LevelInfo
|
||||
LevelWarn = slog.LevelWarn
|
||||
LevelError = slog.LevelError
|
||||
)
|
||||
|
||||
// Logger wraps slog.Logger to provide a consistent logging interface
|
||||
type Logger struct {
|
||||
*slog.Logger
|
||||
}
|
||||
|
||||
// Config holds the configuration for the logger
|
||||
type Config struct {
|
||||
Level Level
|
||||
Format string // "json" or "text"
|
||||
Writer io.Writer
|
||||
AddSource bool
|
||||
ContextKeys []string
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default logging configuration
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Level: LevelInfo,
|
||||
Format: "json",
|
||||
Writer: os.Stderr,
|
||||
AddSource: false,
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Logger with the given configuration
|
||||
func New(cfg *Config) *Logger {
|
||||
if cfg == nil {
|
||||
cfg = DefaultConfig()
|
||||
}
|
||||
|
||||
var handler slog.Handler
|
||||
|
||||
handlerOpts := &slog.HandlerOptions{
|
||||
Level: cfg.Level,
|
||||
AddSource: cfg.AddSource,
|
||||
}
|
||||
|
||||
if cfg.Format == "json" {
|
||||
handler = slog.NewJSONHandler(cfg.Writer, handlerOpts)
|
||||
} else {
|
||||
handler = slog.NewTextHandler(cfg.Writer, handlerOpts)
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
Logger: slog.New(handler),
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext adds context values to the logger
|
||||
func (l *Logger) WithContext(ctx context.Context) *Logger {
|
||||
logger := l.Logger
|
||||
|
||||
// Extract values from context and add them to the logger
|
||||
// This can be expanded to include specific context keys
|
||||
// based on the application's needs
|
||||
|
||||
return &Logger{
|
||||
Logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// WithField adds a field to the logger
|
||||
func (l *Logger) WithField(key string, value interface{}) *Logger {
|
||||
return &Logger{
|
||||
Logger: l.Logger.With(key, value),
|
||||
}
|
||||
}
|
||||
|
||||
// WithFields adds multiple fields to the logger
|
||||
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
|
||||
logger := l.Logger
|
||||
|
||||
for k, v := range fields {
|
||||
logger = logger.With(k, v)
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
Logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Global default logger
|
||||
var defaultLogger = New(DefaultConfig())
|
||||
|
||||
// SetDefault sets the default logger
|
||||
func SetDefault(logger *Logger) {
|
||||
defaultLogger = logger
|
||||
slog.SetDefault(logger.Logger)
|
||||
}
|
||||
|
||||
// Default returns the default logger
|
||||
func Default() *Logger {
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// GetLoggerFromContext retrieves a logger from the context
|
||||
// If no logger is found in the context, the default logger is returned
|
||||
func GetLoggerFromContext(ctx context.Context) *Logger {
|
||||
if ctx == nil {
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// Check for logger in context
|
||||
if loggerValue := ctx.Value(loggerKey{}); loggerValue != nil {
|
||||
if logger, ok := loggerValue.(*Logger); ok {
|
||||
return logger
|
||||
}
|
||||
}
|
||||
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// loggerKey is used as the key for storing the logger in the context
|
||||
type loggerKey struct{}
|
||||
|
||||
// ContextWithLogger returns a new context with the logger
|
||||
func ContextWithLogger(ctx context.Context, logger *Logger) context.Context {
|
||||
return context.WithValue(ctx, loggerKey{}, logger)
|
||||
}
|
||||
|
||||
// Convenience methods to use the default logger
|
||||
func Debug(msg string, args ...interface{}) {
|
||||
defaultLogger.Debug(msg, args...)
|
||||
}
|
||||
|
||||
func Info(msg string, args ...interface{}) {
|
||||
defaultLogger.Info(msg, args...)
|
||||
}
|
||||
|
||||
func Warn(msg string, args ...interface{}) {
|
||||
defaultLogger.Warn(msg, args...)
|
||||
}
|
||||
|
||||
func Error(msg string, args ...interface{}) {
|
||||
defaultLogger.Error(msg, args...)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue