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