From 719aee56e8ccbc287cef6e1ffedac22b00b3ba2f Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Tue, 20 May 2025 22:09:10 +0800 Subject: [PATCH] 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 --- CLAUDE.md | 168 +++++++++++++++ docs/DESIGN.md | 415 ++++++++++++++++++++++++++++++++++++++ docs/PROJECT_PLAN.md | 315 +++++++++++++++++++++++++++++ docs/README.md | 72 +++++++ go.mod | 3 + internal/errors/errors.go | 251 +++++++++++++++++++++++ pkg/auth2/auth2.go | 172 ++++++++++++++++ pkg/auth2/version.go | 37 ++++ pkg/config/config.go | 404 +++++++++++++++++++++++++++++++++++++ pkg/log/log.go | 156 ++++++++++++++ 10 files changed, 1993 insertions(+) create mode 100644 CLAUDE.md create mode 100644 docs/DESIGN.md create mode 100644 docs/PROJECT_PLAN.md create mode 100644 docs/README.md create mode 100644 go.mod create mode 100644 internal/errors/errors.go create mode 100644 pkg/auth2/auth2.go create mode 100644 pkg/auth2/version.go create mode 100644 pkg/config/config.go create mode 100644 pkg/log/log.go diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e8cebff --- /dev/null +++ b/CLAUDE.md @@ -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 diff --git a/docs/DESIGN.md b/docs/DESIGN.md new file mode 100644 index 0000000..bb15ef8 --- /dev/null +++ b/docs/DESIGN.md @@ -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 \ No newline at end of file diff --git a/docs/PROJECT_PLAN.md b/docs/PROJECT_PLAN.md new file mode 100644 index 0000000..18def51 --- /dev/null +++ b/docs/PROJECT_PLAN.md @@ -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 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..134df7d --- /dev/null +++ b/docs/README.md @@ -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 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..26a04ec --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/Fishwaldo/auth2 + +go 1.24 \ No newline at end of file diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 0000000..06007a3 --- /dev/null +++ b/internal/errors/errors.go @@ -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) +} \ No newline at end of file diff --git a/pkg/auth2/auth2.go b/pkg/auth2/auth2.go new file mode 100644 index 0000000..fe427cf --- /dev/null +++ b/pkg/auth2/auth2.go @@ -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 +} \ No newline at end of file diff --git a/pkg/auth2/version.go b/pkg/auth2/version.go new file mode 100644 index 0000000..9e7862d --- /dev/null +++ b/pkg/auth2/version.go @@ -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 +} \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..47d92c4 --- /dev/null +++ b/pkg/config/config.go @@ -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 +} \ No newline at end of file diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..867bd87 --- /dev/null +++ b/pkg/log/log.go @@ -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...) +} \ No newline at end of file