core

package
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package core provides the high-level authentication and authorization business logic. It orchestrates the low-level data operations from the auth package and adds features like password hashing, session management, rate limiting, audit logging, and more.

The core package is organized around specialized service objects:

  • AuthService: Main orchestrator that coordinates all sub-services
  • UserService: User identity management
  • AccountService: Provider-specific account management and authentication
  • SessionService: Session creation, validation, and caching
  • VerificationService: Token generation and validation for email/password flows

All services accept context for cancellation and use the audit logger for security event tracking.

Index

Constants

View Source
const (
	// DefaultRateLimitRequests is the default number of requests allowed per window
	// for general API endpoints (100 requests/minute is suitable for most applications)
	DefaultRateLimitRequests = 100

	// DefaultRateLimitWindow is the default time window for rate limiting (1 minute)
	DefaultRateLimitWindow = time.Minute

	// DefaultRateLimitKeyPrefix is the default Redis key prefix for rate limit counters
	DefaultRateLimitKeyPrefix = "aegis:ratelimit:"

	// AuthRateLimitRequests is a stricter limit for authentication endpoints
	// (10 requests/minute prevents brute force while allowing legitimate retries)
	AuthRateLimitRequests = 10

	// AuthRateLimitKeyPrefix is the Redis key prefix for auth-specific rate limits
	AuthRateLimitKeyPrefix = "aegis:ratelimit:auth:"
)

Default rate limiting constants control API request throttling. Separate limits are defined for general endpoints vs. authentication endpoints.

View Source
const (
	// DefaultMaxLoginAttempts is the maximum number of failed login attempts allowed
	// before triggering account lockout (5 attempts is OWASP-recommended)
	DefaultMaxLoginAttempts = 5

	// DefaultLoginLockoutDuration is how long to lock out after max attempts
	// (15 minutes balances security with user convenience)
	DefaultLoginLockoutDuration = 15 * time.Minute

	// DefaultLoginAttemptWindow is the time window for counting attempts
	// (attempts older than this are not counted toward the limit)
	DefaultLoginAttemptWindow = 15 * time.Minute
)

Default login attempt tracking constants prevent brute force attacks. After exceeding max attempts, accounts are temporarily locked.

View Source
const (
	// DefaultPasswordMinLength is the minimum password length (8 characters)
	// NIST recommends 8+ characters for user-chosen passwords
	DefaultPasswordMinLength = 8

	// DefaultPasswordMaxLength is the maximum password length (128 characters)
	// Prevents DoS attacks via extremely long password hashing.
	// 0 would mean no limit.
	DefaultPasswordMaxLength = 128
)

Default password policy constants enforce minimum security requirements.

View Source
const (
	// DefaultSessionExpiry is the default session expiration time (24 hours)
	// After this, users must re-authenticate or use refresh token
	DefaultSessionExpiry = 24 * time.Hour

	// DefaultRefreshExpiry is the default refresh token expiration time (7 days)
	// Allows "remember me" functionality while limiting token lifetime
	DefaultRefreshExpiry = 7 * 24 * time.Hour
)

Default session configuration constants control session lifetimes.

View Source
const (
	// DefaultArgon2Time is the number of iterations (time cost)
	// Higher = slower hashing = more brute force resistant
	DefaultArgon2Time = 1

	// DefaultArgon2Memory is the memory cost in KiB (64 MB)
	// Higher memory makes GPU attacks less effective
	DefaultArgon2Memory = 64 * 1024

	// DefaultArgon2Threads is the degree of parallelism
	// Should match typical server CPU core count
	DefaultArgon2Threads = 4

	// DefaultArgon2KeyLength is the derived key length in bytes (256 bits)
	DefaultArgon2KeyLength = 32
)

Default password hashing constants use Argon2id parameters. These values are based on OWASP recommendations for 2024 and balance security (resistance to attacks) with performance (server load).

View Source
const (
	// TokenLength is the length of generated tokens in bytes (32 bytes = 256 bits)
	// Provides sufficient entropy to prevent guessing attacks
	TokenLength = 32

	// SaltLength is the length of password salt in bytes (16 bytes = 128 bits)
	// Ensures unique hash outputs even for identical passwords
	SaltLength = 16
)

Token generation constants define entropy for cryptographic tokens.

View Source
const (
	// RedisSessionPrefix is the Redis key prefix for session storage
	RedisSessionPrefix = "aegis:session:"

	// RedisRefreshTokenPrefix is the Redis key prefix for refresh tokens
	RedisRefreshTokenPrefix = "aegis:refresh:"

	// RedisUserSessionsPrefix is the Redis key prefix for user session sets
	// (used to track all sessions for a user for "logout all devices")
	RedisUserSessionsPrefix = "aegis:user_sessions:"

	// RedisLoginAttemptsPrefix is the Redis key prefix for login attempt counters
	RedisLoginAttemptsPrefix = "aegis:login_attempts:"
)

Redis key prefixes prevent key collisions when using shared Redis instances. All Aegis keys are prefixed with "aegis:" for easy identification.

View Source
const (
	// DefaultCookieName is the default session cookie name
	DefaultCookieName = "aegis_session"
	// DefaultCookiePath is the default cookie path
	DefaultCookiePath = "/"
	// DefaultCookieSameSite is the default SameSite attribute
	DefaultCookieSameSite = "Lax"
	// DefaultCookieHTTPOnly is the default HttpOnly attribute
	DefaultCookieHTTPOnly = true
	// DefaultCookieSecure is the default Secure attribute
	// Ensures cookies are only sent over HTTPS in production
	DefaultCookieSecure = true
)

Default cookie settings for session management.

View Source
const (
	// UppercaseStart is the start of uppercase ASCII range
	UppercaseStart = 'A'
	// UppercaseEnd is the end of uppercase ASCII range
	UppercaseEnd = 'Z'
	// LowercaseStart is the start of lowercase ASCII range
	LowercaseStart = 'a'
	// LowercaseEnd is the end of lowercase ASCII range
	LowercaseEnd = 'z'
	// DigitStart is the start of digit ASCII range
	DigitStart = '0'
	// DigitEnd is the end of digit ASCII range
	DigitEnd = '9'
	// SpecialRange1Start is the start of first special character range
	SpecialRange1Start = '!'
	// SpecialRange1End is the end of first special character range
	SpecialRange1End = '/'
	// SpecialRange2Start is the start of second special character range
	SpecialRange2Start = ':'
	// SpecialRange2End is the end of second special character range
	SpecialRange2End = '@'
	// SpecialRange3Start is the start of third special character range
	SpecialRange3Start = '['
	// SpecialRange3End is the end of third special character range
	SpecialRange3End = '`'
	// SpecialRange4Start is the start of fourth special character range
	SpecialRange4Start = '{'
	// SpecialRange4End is the end of fourth special character range
	SpecialRange4End = '~'
)

Character range constants for password validation

View Source
const (
	// AuthErrorCodeInvalidCredentials indicates wrong username/password
	// #nosec G101
	AuthErrorCodeInvalidCredentials = "INVALID_CREDENTIALS"

	// AuthErrorCodeUserNotFound indicates user does not exist
	AuthErrorCodeUserNotFound = "USER_NOT_FOUND"

	// AuthErrorCodeUserDisabled indicates account is deactivated
	AuthErrorCodeUserDisabled = "USER_DISABLED"

	// AuthErrorCodeAccountNotFound indicates no account for this provider
	AuthErrorCodeAccountNotFound = "ACCOUNT_NOT_FOUND"

	// AuthErrorCodeTokenInvalid indicates malformed token
	AuthErrorCodeTokenInvalid = "TOKEN_INVALID"

	// AuthErrorCodeTokenExpired indicates token lifetime exceeded
	AuthErrorCodeTokenExpired = "TOKEN_EXPIRED"

	// AuthErrorCodeSessionInvalid indicates invalid session
	AuthErrorCodeSessionInvalid = "SESSION_INVALID"

	// AuthErrorCodeRateLimit indicates too many requests
	AuthErrorCodeRateLimit = "RATE_LIMIT"

	// AuthErrorCodeUnauthorized indicates authentication required
	AuthErrorCodeUnauthorized = "UNAUTHORIZED"

	// AuthErrorCodeInternal indicates an unexpected server error
	AuthErrorCodeInternal = "INTERNAL_ERROR"
)

Predefined auth error codes for API responses. These codes provide stable identifiers that clients can programmatically handle without parsing error messages.

View Source
const (
	// DefaultMaxBodySize is the default maximum request body size (1MB)
	// Suitable for most API endpoints with JSON payloads
	DefaultMaxBodySize int64 = 1 << 20 // 1 MB

	// MaxBodySizeSmall is for endpoints with small payloads like login (64KB)
	// Use for authentication endpoints to prevent abuse
	MaxBodySizeSmall int64 = 64 << 10 // 64 KB

	// MaxBodySizeLarge is for endpoints that may have larger payloads (10MB)
	// Use for file uploads or bulk operations
	MaxBodySizeLarge int64 = 10 << 20 // 10 MB
)

Default request body size limits prevent denial-of-service attacks via extremely large request bodies. These limits can be overridden per-route.

View Source
const (
	// Core entity schemas
	SchemaUser            = "User"
	SchemaEnrichedUser    = "EnrichedUser"
	SchemaSession         = "Session"
	SchemaSessionWithUser = "SessionWithUser"

	// Common response schemas
	SchemaError   = "Error"
	SchemaSuccess = "Success"

	// Request schemas
	SchemaRefreshTokenRequest = "RefreshTokenRequest"
	SchemaLoginRequest        = "LoginRequest"
	SchemaRegisterRequest     = "RegisterRequest"

	// List schemas
	SchemaSessionList = "SessionList"
)

Core schema names for OpenAPI documentation.

These constants provide type-safe references to OpenAPI schema names used throughout the Aegis framework. They are used by:

  • RouteMetadata: Annotating HTTP handlers with request/response schemas
  • OpenAPI plugin: Generating OpenAPI 3.0 specifications
  • API documentation: Auto-generating API docs from code

Benefits of using constants:

  • Type safety: Compile-time checking (no typos in schema names)
  • Refactoring: Easy to rename schemas across the codebase
  • Discoverability: IDE autocomplete shows available schemas

Naming convention:

  • Singular for entities: SchemaUser, not SchemaUsers
  • Descriptive suffixes: SchemaUserList for lists, SchemaLoginRequest for requests

Example usage in RouteMetadata:

metadata := &core.RouteMetadata{
	RequestBody: &core.RequestBodyMeta{
		Schema: core.SchemaLoginRequest,
	},
	Responses: map[string]*core.ResponseMeta{
		"200": {Schema: core.SchemaUser},
		"401": {Schema: core.SchemaError},
	},
}
View Source
const DefaultSecretLength = 32

DefaultSecretLength is the recommended length for derived secrets (256 bits / 32 bytes).

This provides 256-bit security, which is the standard for symmetric encryption and HMAC operations. Use this constant when calling DeriveSecret:

secret := core.DeriveSecret(master, "purpose", core.DefaultSecretLength)
View Source
const EmailRegexPattern = `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`

EmailRegexPattern is the regex pattern for email validation (RFC 5322 simplified)

View Source
const (
	// PasswordProvider is the provider name for password authentication
	PasswordProvider = "password"
)

Provider constants

Variables

View Source
var (
	// ErrUserNotFound indicates the requested user does not exist
	ErrUserNotFound = errors.New("user not found")

	// ErrInvalidCredentials indicates incorrect username/password combination
	ErrInvalidCredentials = errors.New("invalid credentials")

	// ErrUserDisabled indicates the user account has been deactivated
	ErrUserDisabled = errors.New("user account is disabled")

	// ErrEmailNotVerified indicates email verification is required
	ErrEmailNotVerified = errors.New("email not verified")

	// ErrInvalidToken indicates a malformed or invalid token
	ErrInvalidToken = errors.New("invalid token")

	// ErrTokenExpired indicates the token has exceeded its lifetime
	ErrTokenExpired = errors.New("token expired")

	// ErrSessionNotFound indicates the session does not exist
	ErrSessionNotFound = errors.New("session not found")

	// ErrInvalidSession indicates a malformed or corrupted session
	ErrInvalidSession = errors.New("invalid session")

	// ErrSessionExpired indicates the session has exceeded its lifetime
	ErrSessionExpired = errors.New("session expired")

	// ErrRateLimitExceeded indicates too many requests from this client
	ErrRateLimitExceeded = errors.New("rate limit exceeded")

	// ErrInvalidRequest indicates malformed request data
	ErrInvalidRequest = errors.New("invalid request")

	// ErrUnauthorized indicates authentication is required
	ErrUnauthorized = errors.New("unauthorized")

	// ErrForbidden indicates the authenticated user lacks permissions
	ErrForbidden = errors.New("forbidden")

	// ErrInternalServer indicates an unexpected server error
	ErrInternalServer = errors.New("internal server error")

	// ErrDatabaseConnection indicates database connectivity issues
	ErrDatabaseConnection = errors.New("database connection error")

	// ErrRedisConnection indicates Redis connectivity issues
	ErrRedisConnection = errors.New("redis connection error")
)

Common authentication errors used throughout the framework. These sentinel errors can be compared using errors.Is() and provide consistent error handling across the application.

Functions

func AegisContextMiddleware

func AegisContextMiddleware() func(http.Handler) http.Handler

AegisContextMiddleware initializes the Aegis request context for each HTTP request.

This middleware is called automatically by the Aegis framework and sets up:

  • Request ID: Unique identifier for tracing and logging
  • Request metadata: IP address, user agent, method, path
  • Plugin data store: Key-value storage for plugins to share data
  • Initialization marker: Prevents double initialization

The middleware is idempotent - if the context is already initialized (e.g., by a parent middleware), it skips initialization and passes through.

Users typically don't need to call this directly - it's integrated into the Aegis HTTP stack automatically.

func AuthMiddleware

func AuthMiddleware(sessionService *SessionService) func(http.Handler) http.Handler

AuthMiddleware returns HTTP middleware that validates sessions and injects authenticated user data into the request context.

Authentication flow:

  1. Check if context already initialized (if not, initialize it)
  2. Look for session in per-request cache (avoid redundant lookups)
  3. Extract session token from cookie or Authorization header
  4. Validate the session token (database + Redis cache)
  5. Load the associated user
  6. Create EnrichedUser and populate with plugin extensions
  7. Inject user and session into context

After this middleware, authenticated requests can access:

  • core.GetUser(ctx) - Get the base user
  • core.GetEnrichedUser(ctx) - Get user with plugin extensions
  • core.GetSession(ctx) - Get the current session

Unauthenticated requests continue normally (no user/session in context). Protected routes should check for authentication and return 401 if missing.

The middleware caches the session in the request context to avoid redundant database/Redis lookups if multiple handlers need authentication data.

Parameters:

  • sessionService: Service for session validation and user lookup

Example usage:

protectedRouter.Use(core.AuthMiddleware(sessionService))

func Authenticated

func Authenticated(ctx context.Context) bool

Authenticated checks if the context has an authenticated user.

func BindAndValidate

func BindAndValidate[T interface{ Validate() error }](r *http.Request) (T, error)

BindAndValidate decodes a JSON request body and validates it. T must implement a Validate() error method. This helper ensures consistent validation across all handlers.

Example usage:

req, err := core.BindAndValidate[CreateOrganizationRequest](r)
if err != nil {
    core.WriteValidationError(w, err)
    return
}

func ClampIntToInt32

func ClampIntToInt32(n int) int32

ClampIntToInt32 safely converts an `int` to `int32` by clamping the value to the valid range for int32. This prevents unsafe downcasts on platforms where `int` is larger than 32 bits (e.g., amd64) and guards against potential overflows when values originate from untrusted sources.

func DeriveSecret

func DeriveSecret(masterSecret []byte, purpose string, length int) []byte

DeriveSecret derives a purpose-specific secret from a master secret using HKDF-SHA256.

HKDF (HMAC-based Key Derivation Function) is a cryptographic key derivation function that allows safely deriving multiple purpose-specific keys from a single master secret.

Why use HKDF instead of reusing the master secret?

  • Cryptographic separation: Each purpose gets a unique, independent key
  • Security isolation: Compromise of one derived key doesn't affect others
  • Standard practice: Recommended by NIST SP 800-108 and RFC 5869

Common use cases in Aegis:

  • CSRF token signing
  • OAuth state token encryption
  • JWT signing keys
  • Cookie encryption keys
  • API key derivation

Parameters:

  • masterSecret: The master secret (minimum 32 bytes recommended)
  • purpose: Unique identifier for this key's purpose (e.g., "csrf", "oauth-state", "jwt")
  • length: Output length in bytes (typically 32 for 256-bit keys)

Security notes:

  • Different purposes MUST use different purpose strings
  • Master secret should be cryptographically random (32+ bytes)
  • Output keys are deterministic (same inputs → same output)

Example:

masterSecret := []byte("your-32-byte-master-secret-here!")
// Derive separate keys for different purposes
csrfSecret := core.DeriveSecret(masterSecret, "csrf", 32)
oauthSecret := core.DeriveSecret(masterSecret, "oauth-state", 32)
jwtSecret := core.DeriveSecret(masterSecret, "jwt-signing", 32)

func ExtendUser

func ExtendUser(ctx context.Context, key string, value any)

ExtendUser adds data to the enriched user in context. If no enriched user exists, this is a no-op. Plugins should call this in their middleware or handlers to add their data.

Example:

core.ExtendUser(ctx, "admin:role", "admin")
core.ExtendUser(ctx, "orgs:memberships", []string{"org1", "org2"})

func GenerateID

func GenerateID() string

GenerateID generates a unique identifier using the configured ID strategy.

This is the primary ID generation function used throughout Aegis for:

  • User IDs
  • Session IDs
  • Account IDs
  • Verification token IDs

Default Strategy: ULID

  • Format: "01ARZ3NDEKTSV4RRFFQ69G5FAV" (26 characters)
  • Sortable by creation time
  • Database-friendly indexing
  • No configuration required

Strategy Selection:

  • ULID (default): Best for most use cases

  • Sortable IDs improve database performance

  • Compact format (26 chars vs 36 for UUID)

  • Built-in timestamp makes debugging easier

  • UUID: When you need standard UUID format

  • Format: "550e8400-e29b-41d4-a716-446655440000"

  • Maximum randomness (122 bits)

  • Not sortable (random ordering)

  • Custom: For specialized requirements

  • Implement IDGeneratorFunc

  • Examples: KSUID, Snowflake, nanoid, database sequences

Usage Examples:

// Default (ULID)
userID := core.GenerateID() // "01ARZ3NDEKTSV4RRFFQ69G5FAV"

// Switch to UUID
core.SetIDStrategy(core.IDStrategyUUID)
userID := core.GenerateID() // "550e8400-e29b-41d4-a716-446655440000"

// Use custom generator
core.SetCustomIDGenerator(func() string { return ksuid.New().String() })
userID := core.GenerateID() // Custom format

Note: For database-generated IDs (SERIAL, AUTO_INCREMENT), configure your database schema to generate IDs and don't call this function.

func GenerateOTPCode

func GenerateOTPCode(length int) (string, error)

GenerateOTPCode generates a random numeric OTP (One-Time Password) code.

The code uses cryptographically secure randomness (crypto/rand) and includes leading zeros to ensure the specified length.

Parameters:

  • length: Number of digits (typically 4-8)

Common lengths:

  • 6 digits: Standard for most 2FA systems (Google Authenticator, etc.)
  • 4 digits: Short codes for SMS (balance security vs user convenience)
  • 8 digits: High-security scenarios

Returns a numeric string with leading zeros if necessary.

Example:

// Generate 6-digit code
code, _ := core.GenerateOTPCode(6) // "042816", "912345", etc.

// Generate 4-digit code for SMS
code, _ := core.GenerateOTPCode(4) // "0042", "9123", etc.

func GetClientIP

func GetClientIP(r *http.Request) string

GetClientIP extracts the client IP address from the request. It checks headers in order: X-Forwarded-For, X-Real-IP, then falls back to RemoteAddr.

func GetIPAddress

func GetIPAddress(ctx context.Context) string

GetIPAddress extracts the IP address from context metadata. Falls back to extracting from request if not in context.

func GetPathParam

func GetPathParam(r *http.Request, name string) string

GetPathParam extracts a path parameter from the request using the router's path param function stored in context. Falls back to Go 1.22+ PathValue.

func GetPluginValue

func GetPluginValue(ctx context.Context, key string) any

GetPluginValue is a convenience function to get a plugin value directly from context. Returns nil if plugin data is not initialized or key doesn't exist.

func GetRequestID

func GetRequestID(ctx context.Context) string

GetRequestID extracts the request ID from the context. Returns empty string if not set.

func GetSanitizedPathParam

func GetSanitizedPathParam(r *http.Request, name string) string

GetSanitizedPathParam extracts and sanitizes a path parameter. This is the recommended way to retrieve IDs and other path parameters from the URL path.

func GetSession

func GetSession(ctx context.Context) *auth.Session

GetSession extracts the session from the context. Returns nil if no session is present. This acts as a per-request cache - once a session is stored in context, it can be retrieved without hitting the database or Redis again.

func GetUser

func GetUser(ctx context.Context) (*auth.User, error)

GetUser extracts the user from the context. Returns an error if no user is present (not authenticated).

func GetUserAgent

func GetUserAgent(ctx context.Context) string

GetUserAgent extracts the user agent from context metadata.

func GetUserExtension

func GetUserExtension(ctx context.Context, key string) any

GetUserExtension retrieves a specific extension from the enriched user. Returns nil if user is not authenticated or extension doesn't exist.

func GetUserExtensionBool

func GetUserExtensionBool(ctx context.Context, key string) bool

GetUserExtensionBool retrieves a bool extension from the enriched user.

func GetUserExtensionString

func GetUserExtensionString(ctx context.Context, key string) string

GetUserExtensionString retrieves a string extension from the enriched user.

func GetUserID

func GetUserID(ctx context.Context) string

GetUserID is a convenience function to get just the user ID from context. Returns empty string if not authenticated.

func HasSession

func HasSession(ctx context.Context) bool

HasSession checks if a session exists in context (already validated for this request). Use this to avoid redundant session validation within the same request.

func HashPassword

func HashPassword(password string, time, memory uint32, threads uint8, keyLen uint32) (string, error)

HashPassword creates a secure password hash using the Argon2id algorithm.

The function generates a cryptographically random salt and derives a hash from the password using the specified parameters. The result is encoded in PHC string format, which includes all parameters needed for later verification.

Parameters:

  • password: The plaintext password to hash
  • time: Number of iterations (higher = slower but more secure). Use 0 for defaults.
  • memory: Memory usage in KiB (higher = more RAM needed). Use 0 for defaults.
  • threads: Degree of parallelism (typically CPU core count). Use 0 for defaults.
  • keyLen: Length of the derived key in bytes. Use 0 for defaults.

Returns a PHC-formatted string like:

$argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash>

This format is portable and includes the version, parameters, salt, and hash, allowing verification even if default parameters change in the future.

Example:

hash, err := HashPassword("my-secret-password", 0, 0, 0, 0)
// hash = "$argon2id$v=19$m=65536,t=3,p=4$..."

func HashShort

func HashShort(s string) string

HashShort returns a short hash string (hex) of the input suitable for non-reversible identification in logs.

func IsAuthError

func IsAuthError(err error) bool

IsAuthError checks if an error is an auth error

func IsContextInitialized

func IsContextInitialized(ctx context.Context) bool

IsContextInitialized checks if AegisContextMiddleware has been run. This is used internally to ensure proper middleware chain ordering.

func IsValidationError

func IsValidationError(err error) bool

IsValidationError checks if an error is a validation error

func MaxBodySizeMiddleware

func MaxBodySizeMiddleware(maxBytes int64) func(http.Handler) http.Handler

MaxBodySizeMiddleware returns middleware that limits request body size. This helps prevent DoS attacks via large request bodies.

Example:

router.Use(core.MaxBodySizeMiddleware(core.DefaultMaxBodySize))

func MustGetUser

func MustGetUser(ctx context.Context) *auth.User

MustGetUser extracts the user from context, panicking if not found. Use this only in handlers where authentication is guaranteed by middleware.

func NormalizeWhitespace

func NormalizeWhitespace(input string) string

NormalizeWhitespace collapses multiple spaces into single spaces.

Example:

text := core.NormalizeWhitespace("Hello    World  \n  Test")
// Output: "Hello World Test"

func RateLimitMiddleware

func RateLimitMiddleware(limiter *RateLimiter) func(http.Handler) http.Handler

RateLimitMiddleware creates a middleware that rate limits requests

func RedactForLog

func RedactForLog(s string) string

RedactForLog returns a masked version of a user-provided identifier suitable for inclusion in logs. It preserves a small, non-sensitive hint while removing the majority of the value to avoid leaking sensitive data.

Examples:

  • email: "alice@example.com" -> "a***@example.com"
  • phone: "+1234567890" -> "+1******90"
  • other strings: "userid-abcdef" -> "us***ef"

func RequireAuthMiddleware

func RequireAuthMiddleware(_ *SessionService) func(http.Handler) http.Handler

RequireAuthMiddleware returns middleware that requires authentication.

func SanitizeEmail

func SanitizeEmail(email string) string

SanitizeEmail sanitizes and normalizes email addresses.

Email-specific sanitization:

  • Converts to lowercase (emails are case-insensitive)
  • Removes whitespace
  • Removes dangerous characters
  • Validates basic format

Note: This does NOT validate email format. Use ValidateEmail() for validation.

Example:

email := core.SanitizeEmail("  John.Doe@EXAMPLE.com  ")
// Output: "john.doe@example.com"

func SanitizeFilename

func SanitizeFilename(filename string) string

SanitizeFilename sanitizes filenames to prevent directory traversal attacks.

Filename-specific sanitization:

  • Removes path separators (/, \)
  • Removes null bytes
  • Removes control characters
  • Blocks directory traversal patterns (.., .)
  • Enforces length limits

Example:

filename := core.SanitizeFilename("../../etc/passwd")
// Output: "etcpasswd"

filename := core.SanitizeFilename("my<file>.txt")
// Output: "myfile.txt"

func SanitizeHTML

func SanitizeHTML(content string) string

SanitizeHTML sanitizes HTML content for safe display.

This function escapes HTML entities to prevent XSS attacks while preserving the original text content. Use this when you need to display user-generated content in HTML context.

Example:

content := core.SanitizeHTML("<script>alert('xss')</script>")
// Output: "&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;"

func SanitizeMultiline

func SanitizeMultiline(input string, maxLength int) string

SanitizeMultiline sanitizes multi-line text input.

Multiline-specific sanitization:

  • Preserves newlines and basic formatting
  • Removes dangerous HTML/scripts
  • Normalizes line endings to \n
  • Limits total length

Use this for text areas, descriptions, and comments.

Example:

text := core.SanitizeMultiline("Line 1\r\nLine 2\r\n<script>alert('xss')</script>", 5000)
// Output: "Line 1\nLine 2\n"

func SanitizePhoneNumber

func SanitizePhoneNumber(phone string) string

SanitizePhoneNumber sanitizes phone numbers to a consistent format.

Phone-specific sanitization:

  • Keeps only digits, plus sign, and hyphens
  • Removes all other characters
  • Trims whitespace

Example:

phone := core.SanitizePhoneNumber("+1 (555) 123-4567")
// Output: "+1-555-123-4567"

func SanitizeSQL

func SanitizeSQL(input string) string

SanitizeSQL removes common SQL injection patterns.

WARNING: This is NOT a replacement for parameterized queries! Always use prepared statements or parameterized queries for SQL. This function is only a defense-in-depth measure.

Removes:

  • SQL comments (-- , #, /* */)
  • Semicolons (statement terminators)
  • Null bytes

Example:

input := core.SanitizeSQL("admin' OR '1'='1' --")
// Output: "admin' OR '1'='1' "

func SanitizeString

func SanitizeString(input string, config *SanitizationConfig) string

SanitizeString performs general-purpose string sanitization.

This is the primary sanitization function for most user inputs like names, descriptions, and general text fields. It applies multiple security measures:

  • Removes null bytes (prevents null byte injection)
  • Strips HTML tags (prevents XSS)
  • Removes control characters (prevents terminal injection)
  • Normalizes whitespace (improves data quality)
  • Enforces length limits (prevents DoS)

Parameters:

  • input: The raw user input string
  • config: Sanitization configuration (nil = use defaults)

Example:

name := core.SanitizeString(userInput, nil)
// Input:  "John<script>alert('xss')</script>  Doe  "
// Output: "John Doe"

func SanitizeURL

func SanitizeURL(url string) string

SanitizeURL sanitizes URLs to prevent injection attacks.

URL-specific sanitization:

  • Removes whitespace
  • Blocks javascript: and data: schemes
  • Removes null bytes
  • Validates basic URL structure

Note: This does NOT validate URL format. Use proper URL validation separately.

Example:

url := core.SanitizeURL("  https://example.com/path  ")
// Output: "https://example.com/path"

url := core.SanitizeURL("javascript:alert('xss')")
// Output: "" (dangerous scheme blocked)

func SanitizeUsername

func SanitizeUsername(username string, maxLength int) string

SanitizeUsername sanitizes usernames for safe storage and display.

Username-specific rules:

  • Allows alphanumeric, underscore, hyphen, and period
  • Removes all other characters
  • Converts to lowercase for consistency
  • Enforces length limits

Parameters:

  • username: The raw username input
  • maxLength: Maximum allowed length (0 = use default of 50)

Example:

username := core.SanitizeUsername("John_Doe123!@#", 0)
// Output: "john_doe123"

func SetCustomIDGenerator

func SetCustomIDGenerator(generator IDGeneratorFunc)

SetCustomIDGenerator sets a custom ID generation function.

This automatically switches the ID strategy to IDStrategyCustom. The provided function will be called every time GenerateID() is invoked.

Your custom generator should:

  • Return unique IDs (collision-free)
  • Be thread-safe if called concurrently
  • Generate IDs quickly (called frequently)

Example (using KSUID):

import "github.com/segmentio/ksuid"
core.SetCustomIDGenerator(func() string {
	return ksuid.New().String()
})

Example (using database sequences - NOT recommended for distributed systems):

var counter uint64
core.SetCustomIDGenerator(func() string {
	return fmt.Sprintf("%d", atomic.AddUint64(&counter, 1))
})

func SetHTTPLogger

func SetHTTPLogger(l HTTPLogger)

SetHTTPLogger sets the logger for HTTP helpers. If set, WriteJSON will log JSON encoding errors instead of silently failing.

Example:

core.SetHTTPLogger(myZapLogger)

func SetIDStrategy

func SetIDStrategy(strategy IDStrategy)

SetIDStrategy sets the global ID generation strategy for the application.

This should be called during application initialization, before any IDs are generated. Changing the strategy after IDs have been generated may cause inconsistent ID formats.

Example:

// Switch to UUID v4
core.SetIDStrategy(core.IDStrategyUUID)

// Use ULID (default)
core.SetIDStrategy(core.IDStrategyULID)

func SetPluginValue

func SetPluginValue(ctx context.Context, key string, value any)

SetPluginValue is a convenience function to set a plugin value directly in context. Does nothing if plugin data is not initialized.

func StripTags

func StripTags(input string) string

StripTags removes all HTML tags from a string.

This is a convenience function for quick HTML tag removal.

Example:

text := core.StripTags("<p>Hello <b>World</b></p>")
// Output: "Hello World"

func ValidateEmail

func ValidateEmail(email string) error

ValidateEmail validates an email address format.

Checks:

  • Email is not empty
  • Email matches RFC 5322 format (using ozzo-validation)

Returns an error if validation fails. Leading/trailing whitespace is trimmed.

Example:

if err := core.ValidateEmail(email); err != nil {
	return fmt.Errorf("invalid email: %w", err)
}

func ValidateMiddleware

func ValidateMiddleware[T interface{ Validate() error }](
	handler func(w http.ResponseWriter, r *http.Request, req T),
) http.HandlerFunc

ValidateMiddleware creates a middleware that automatically validates request bodies. T must implement a Validate() error method. The validated request is passed to the handler, eliminating the need for manual validation.

Example usage:

router.POST("/organizations", ValidateMiddleware(p.CreateOrganizationHandler))

func (p *Plugin) CreateOrganizationHandler(
    w http.ResponseWriter,
    r *http.Request,
    req CreateOrganizationRequest,  // Already validated!
) {
    // Use req directly - validation is guaranteed
}

func ValidatePassword

func ValidatePassword(password string, policy *PasswordPolicyConfig) error

ValidatePassword validates password strength based on a configurable policy.

The validation checks are controlled by the PasswordPolicyConfig:

  • MinLength: Minimum character count (default: 8)
  • MaxLength: Maximum character count (default: 128, prevents DoS)
  • RequireUpper: At least one uppercase letter A-Z
  • RequireLower: At least one lowercase letter a-z
  • RequireDigit: At least one numeric digit 0-9
  • RequireSpecial: At least one special character (!@#$%^&*, etc.)

If policy is nil, DefaultPasswordPolicyConfig is used (8+ chars, mixed case, digit required, special chars optional).

Modern best practices (NIST/OWASP 2024):

  • Enforce minimum length (8+ characters)
  • Optionally require character diversity
  • Check against breached password databases (not implemented here)
  • Don't force regular password changes

Parameters:

  • password: The plaintext password to validate
  • policy: The password policy to enforce (nil = use defaults)

Example:

policy := &core.PasswordPolicyConfig{
	MinLength:      12,
	RequireSpecial: true,
}
if err := core.ValidatePassword(password, policy); err != nil {
	return fmt.Errorf("weak password: %w", err)
}

func ValidatePasswordSimple

func ValidatePasswordSimple(password string, minLength int) error

ValidatePasswordSimple validates password with basic length requirement only.

This is a simplified validator that only checks minimum length, without requiring character diversity (uppercase, lowercase, digits, symbols).

Use this when:

  • Building low-security applications (internal tools, dev environments)
  • Users find strict policies too frustrating
  • You rely on other security measures (MFA, breach detection, etc.)

For production systems with sensitive data, prefer ValidatePassword with a proper PasswordPolicyConfig.

Parameters:

  • password: The plaintext password to validate
  • minLength: Minimum character count (0 = use default of 6)

Example:

if err := core.ValidatePasswordSimple(password, 8); err != nil {
	return err
}

func VerifyPassword

func VerifyPassword(password, encodedHash string) (bool, error)

VerifyPassword verifies a plaintext password against an Argon2id hash.

The function parses the PHC-formatted hash string to extract the algorithm parameters, salt, and expected hash. It then re-hashes the provided password with the same parameters and compares the results using constant-time comparison to prevent timing attacks.

Parameters:

  • password: The plaintext password to verify
  • encodedHash: The PHC-formatted hash string from HashPassword or database

Returns:

  • bool: true if the password matches, false otherwise
  • error: validation error if the hash format is invalid or corrupted

Expected PHC format:

$argon2id$v=19$m=65536,t=3,p=4$<base64-salt>$<base64-hash>

The function parses:

  1. Algorithm identifier (must be "argon2id")
  2. Version (e.g., v=19)
  3. Parameters: m=memory, t=time, p=parallelism
  4. Base64-encoded salt
  5. Base64-encoded hash to verify against

Example:

ok, err := VerifyPassword("my-secret-password", storedHash)
if err != nil {
	return err // Hash is malformed
}
if !ok {
	return errors.New("invalid password")
}

func WithContextInitialized

func WithContextInitialized(ctx context.Context) context.Context

WithContextInitialized marks the context as initialized by Aegis. This is used internally to check if AegisContextMiddleware was called.

func WithEnrichedUser

func WithEnrichedUser(ctx context.Context, eu *EnrichedUser) context.Context

WithEnrichedUser adds an enriched user to the context. This is called by AuthMiddleware after creating the EnrichedUser.

func WithPathParamFunc

func WithPathParamFunc(ctx context.Context, fn PathParamFunc) context.Context

WithPathParamFunc adds a path parameter extraction function to the context. This is called by router middleware to inject the router-specific implementation.

func WithPluginData

func WithPluginData(ctx context.Context, pd *PluginData) context.Context

WithPluginData adds a plugin data store to the context. This is called once per request to initialize the plugin data store.

func WithRequestID

func WithRequestID(ctx context.Context, requestID string) context.Context

WithRequestID adds a request ID to the context for tracing.

func WithRequestMeta

func WithRequestMeta(ctx context.Context, meta *RequestMeta) context.Context

WithRequestMeta adds request metadata to the context. This includes IP address, user agent, method, and path.

func WithSession

func WithSession(ctx context.Context, session *auth.Session) context.Context

WithSession adds a session to the context. This is called by AuthMiddleware along with WithUser.

func WithUser

func WithUser(ctx context.Context, user *auth.User) context.Context

WithUser adds a user to the context. This is typically called by AuthMiddleware after validating a session.

func WrapError

func WrapError(err error, message string) error

WrapError wraps an error with additional context

func WriteJSON

func WriteJSON(w http.ResponseWriter, statusCode int, data any)

WriteJSON writes a JSON response with the given status code and data.

Automatically sets Content-Type header to application/json. If JSON encoding fails and an HTTPLogger is configured (via SetHTTPLogger), the error is logged.

Example:

core.WriteJSON(w, 200, map[string]string{"status": "ok"})

func WriteJSONError

func WriteJSONError(w http.ResponseWriter, statusCode int, message string)

WriteJSONError writes a JSON error response with the given status code and message.

This is a convenience wrapper around WriteJSON for error responses.

Example:

core.WriteJSONError(w, 400, "Invalid request")
// Output: {"error": "Invalid request"}

Types

type AccountModel

type AccountModel interface {
	// GetID returns the unique identifier for this account
	GetID() string

	// SetID assigns a unique identifier to this account
	SetID(string)

	// GetUserID returns the ID of the user this account belongs to
	GetUserID() string

	// SetUserID assigns the owning user's ID
	SetUserID(string)

	// GetProvider returns the authentication provider name (e.g., "credentials", "google")
	GetProvider() string

	// SetProvider assigns the authentication provider name
	SetProvider(string)

	// GetPasswordHash returns the hashed password (for credential-based accounts)
	GetPasswordHash() string

	// SetPasswordHash assigns the hashed password
	SetPasswordHash(string)

	// SetCreatedAt assigns the creation timestamp
	SetCreatedAt(time.Time)

	// SetUpdatedAt assigns the last modification timestamp
	SetUpdatedAt(time.Time)

	// GetExpiresAt returns when OAuth tokens expire (OAuth accounts only)
	GetExpiresAt() time.Time

	// SetExpiresAt assigns the OAuth token expiration time
	SetExpiresAt(time.Time)

	// GetAccessToken returns the OAuth access token (OAuth accounts only)
	GetAccessToken() string

	// SetAccessToken assigns the OAuth access token
	SetAccessToken(string)

	// GetRefreshToken returns the OAuth refresh token (OAuth accounts only)
	GetRefreshToken() string

	// SetRefreshToken assigns the OAuth refresh token
	SetRefreshToken(string)

	// GetProviderAccountID returns the provider-specific user identifier
	GetProviderAccountID() string

	// SetProviderAccountID assigns the provider-specific user identifier
	SetProviderAccountID(string)
}

AccountModel defines the required methods for an account model implementation. Accounts link users to authentication providers (credentials, OAuth, etc.).

type AccountService

type AccountService struct {
	// contains filtered or unexported fields
}

AccountService manages authentication accounts linked to users.

An "account" represents a specific authentication method for a user:

  • Credential account (email/password)
  • OAuth account (Google, GitHub, etc.)
  • Other provider accounts (SAML, LDAP, etc.)

A single user can have multiple accounts (one per provider), enabling multi-provider authentication (login with email OR Google, for example).

Key responsibilities:

  • Account creation and retrieval
  • Password updates with session invalidation
  • Provider-specific account management
  • Audit logging of account changes

func NewAccountService

func NewAccountService(accountStore auth.AccountStore, sessionStore auth.SessionStore, hashConfig *PasswordHasherConfig, authConfig *AuthConfig, auditLogger AuditLogger) *AccountService

NewAccountService creates a new account service with the specified dependencies.

func (*AccountService) CreateAccount

func (s *AccountService) CreateAccount(ctx context.Context, account auth.Account) error

CreateAccount creates a new account

func (*AccountService) DeleteAccount

func (s *AccountService) DeleteAccount(ctx context.Context, id string) error

DeleteAccount deletes a user account by its ID.

func (*AccountService) GetAccountByID

func (s *AccountService) GetAccountByID(ctx context.Context, id string) (auth.Account, error)

GetAccountByID retrieves an account by ID

func (*AccountService) GetAccountsByUserID

func (s *AccountService) GetAccountsByUserID(ctx context.Context, userID string) ([]auth.Account, error)

GetAccountsByUserID retrieves all accounts for a user

func (*AccountService) GetPasswordAccount

func (s *AccountService) GetPasswordAccount(ctx context.Context, userID string) (auth.Account, error)

GetPasswordAccount retrieves the password account for a user

func (*AccountService) UpdatePassword

func (s *AccountService) UpdatePassword(ctx context.Context, userID, newPassword string) error

UpdatePassword changes a user's password and invalidates existing sessions.

Security considerations:

  • The new password is hashed with Argon2id before storage
  • All existing sessions are invalidated (user must re-login)
  • The operation is audited for security monitoring

Flow:

  1. Find the user's password account (provider="credentials")
  2. Hash the new password
  3. Update the account with the new hash
  4. Delete all sessions to force re-authentication
  5. Log the password change event

Parameters:

  • ctx: Request context
  • userID: ID of the user whose password is being changed
  • newPassword: Plaintext new password to hash and store

Returns an error if:

  • User has no password account (OAuth-only user)
  • Password hashing fails
  • Database update fails

Session deletion errors are logged but don't fail the operation.

func (*AccountService) VerifyPassword

func (s *AccountService) VerifyPassword(ctx context.Context, userID, password string) (bool, error)

VerifyPassword verifies a user's password

type AegisContext

type AegisContext struct {
	// contains filtered or unexported fields
}

AegisContext is a builder for creating Aegis-enriched contexts. Useful for testing and programmatic context creation.

func NewAegisContext

func NewAegisContext(ctx context.Context) *AegisContext

NewAegisContext creates a new context builder from an existing context.

func (*AegisContext) Context

func (ac *AegisContext) Context() context.Context

Context returns the built context.

func (*AegisContext) WithExtension

func (ac *AegisContext) WithExtension(key string, value any) *AegisContext

WithExtension adds a user extension to the context. Requires WithUser to be called first.

func (*AegisContext) WithPluginData

func (ac *AegisContext) WithPluginData() *AegisContext

WithPluginData adds plugin data to the context.

func (*AegisContext) WithRequestID

func (ac *AegisContext) WithRequestID(id string) *AegisContext

WithRequestID adds a request ID to the context.

func (*AegisContext) WithRequestMeta

func (ac *AegisContext) WithRequestMeta(meta *RequestMeta) *AegisContext

WithRequestMeta adds request metadata to the context.

func (*AegisContext) WithSession

func (ac *AegisContext) WithSession(session *auth.Session) *AegisContext

WithSession adds a session to the context.

func (*AegisContext) WithUser

func (ac *AegisContext) WithUser(user *auth.User) *AegisContext

WithUser adds a user to the context.

type AuditEvent

type AuditEvent struct {
	// ID is a unique identifier for this event
	ID string `json:"id"`

	// EventType categorizes what happened
	EventType AuditEventType `json:"event_type"`

	// UserID identifies who performed the action (empty if unauthenticated)
	UserID string `json:"user_id,omitempty"`

	// IPAddress of the client that triggered this event
	IPAddress string `json:"ip_address,omitempty"`

	// UserAgent of the client that triggered this event
	UserAgent string `json:"user_agent,omitempty"`

	// Resource identifies what was acted upon (e.g., "user:123", "session:abc")
	Resource string `json:"resource,omitempty"`

	// Action describes what was done (e.g., "create", "update", "delete")
	Action string `json:"action,omitempty"`

	// Details contains additional context specific to this event type
	Details map[string]any `json:"details,omitempty"`

	// Timestamp of when the event occurred
	Timestamp time.Time `json:"timestamp"`

	// Success indicates if the action succeeded
	Success bool `json:"success"`

	// Error contains the error message if Success is false
	Error string `json:"error,omitempty"`
}

AuditEvent represents a structured security audit log entry.

Audit logs enable:

  • Security monitoring and threat detection
  • Compliance reporting (GDPR, SOC 2, HIPAA, etc.)
  • Forensic investigation after incidents
  • User activity tracking

Events should be written to durable storage (database, log aggregator) for retention and analysis.

type AuditEventType

type AuditEventType string

AuditEventType categorizes different types of security and authentication events. These types enable filtering, alerting, and compliance reporting.

const (

	// AuditEventLoginSuccess indicates a successful user authentication
	AuditEventLoginSuccess AuditEventType = "login_success"

	// AuditEventLoginFailed indicates a failed authentication attempt
	// (wrong password, non-existent user, account locked, etc.)
	AuditEventLoginFailed AuditEventType = "login_failed"

	// AuditEventLogout indicates an explicit user logout
	AuditEventLogout AuditEventType = "logout"

	// AuditEventSessionRefresh indicates a session was refreshed using a refresh token
	AuditEventSessionRefresh AuditEventType = "session_refresh"

	// AuditEventSessionExpired indicates a session expired due to timeout
	AuditEventSessionExpired AuditEventType = "session_expired"

	// AuditEventUserCreated indicates a new user account was created
	AuditEventUserCreated AuditEventType = "user_created"

	// AuditEventUserUpdated indicates user data was modified
	AuditEventUserUpdated AuditEventType = "user_updated"

	// AuditEventUserDeleted indicates a user account was deleted
	AuditEventUserDeleted AuditEventType = "user_deleted"

	// AuditEventEmailChanged indicates a user's email address was changed
	AuditEventEmailChanged AuditEventType = "email_changed"

	// AuditEventPasswordChanged indicates a user changed their password
	AuditEventPasswordChanged AuditEventType = "password_changed"

	// AuditEventPasswordReset indicates a password was reset via recovery flow
	AuditEventPasswordReset AuditEventType = "password_reset"

	// AuditEventRateLimitHit indicates a client exceeded rate limits
	AuditEventRateLimitHit AuditEventType = "rate_limit_hit"

	// AuditEventAccountLocked indicates an account was locked due to failed attempts
	AuditEventAccountLocked AuditEventType = "account_locked"

	// AuditEventSuspiciousActivity indicates anomalous behavior was detected
	AuditEventSuspiciousActivity AuditEventType = "suspicious_activity"
)

type AuditLogger

type AuditLogger interface {
	// LogEvent records a detailed audit event.
	// Should not block - consider async/buffered implementations for high throughput.
	LogEvent(ctx context.Context, event *AuditEvent) error

	// LogAuthEvent is a convenience method for common authentication events.
	// Creates and logs an AuditEvent with authentication-specific fields.
	// IP address and user agent are automatically extracted from the request
	// context (populated by AegisContextMiddleware).
	LogAuthEvent(ctx context.Context, eventType AuditEventType, userID string, success bool, details map[string]any) error
}

AuditLogger defines the interface for audit event logging. Implementations can write to databases, files, log aggregators (Splunk, Elasticsearch), or SIEM systems.

type AuthConfig

type AuthConfig struct {
	// EnableEmailPassword controls whether email/password authentication is available.
	// When false, users cannot signup or login with credentials (OAuth/SSO only).
	EnableEmailPassword bool

	// PasswordPolicy defines password strength requirements for signup/change.
	// If nil, uses DefaultPasswordPolicyConfig (8+ chars, mixed case, digit required).
	PasswordPolicy *PasswordPolicyConfig

	// InvalidateSessionsOnPasswordChange, when true, logs users out from all
	// devices when their password changes. This is a security best practice that
	// prevents attackers from maintaining access after a password is reset.
	// Recommended: true
	InvalidateSessionsOnPasswordChange bool

	// UserFields controls which plugin extension fields are included in user
	// API responses. If nil, all extension fields are included.
	// Use this to limit what data is exposed in user objects.
	UserFields *UserFieldsConfig
}

AuthConfig defines core authentication system configuration. This is the primary configuration struct passed to NewAuthService.

func DefaultAuthConfig

func DefaultAuthConfig() *AuthConfig

DefaultAuthConfig returns default authentication configuration

type AuthError

type AuthError struct {
	// Code is a machine-readable error code (e.g., "INVALID_CREDENTIALS")
	Code string

	// Message is a human-readable error description
	Message string

	// Cause is the underlying error that triggered this auth error (optional)
	Cause error
}

AuthError represents an authentication-specific error with additional context. It wraps an optional cause error and includes a machine-readable code for API responses.

func NewAuthError

func NewAuthError(code, message string) *AuthError

NewAuthError creates a new AuthError with the given code and message.

func NewAuthErrorWithCause

func NewAuthErrorWithCause(code, message string, cause error) *AuthError

NewAuthErrorWithCause creates a new AuthError wrapping an underlying cause.

func (AuthError) Error

func (e AuthError) Error() string

func (AuthError) Unwrap

func (e AuthError) Unwrap() error

Unwrap returns the underlying cause error for error chain unwrapping.

type AuthService

type AuthService struct {

	// Sub-services for specialized operations
	User          *UserService
	Account       *AccountService
	Session       *SessionService
	Verification  *VerificationService
	EmailPassword *EmailPasswordHandlers
	// contains filtered or unexported fields
}

AuthService is the main orchestrator for authentication operations. It coordinates specialized sub-services and provides centralized access to authentication functionality throughout the application.

AuthService manages:

  • Password hashing configuration
  • Audit logging
  • Login attempt tracking for account lockout
  • Authentication policy configuration

It provides four sub-services that handle specific domains:

  • User: User CRUD operations
  • Account: Authentication account management
  • Session: Session lifecycle and caching
  • Verification: Token-based verification flows

AuthService should be initialized once at application startup and shared across HTTP handlers and middleware.

func NewAuthService

func NewAuthService(authConfig *AuthConfig, authConn *auth.Auth, hashConfig *PasswordHasherConfig, auditLogger AuditLogger, loginAttemptTracker *LoginAttemptTracker) *AuthService

NewAuthService creates a new AuthService with all sub-services initialized.

Parameters:

  • authConfig: Authentication policy configuration (session duration, password policy, etc.). If nil, defaults are used.
  • authConn: Connection to the auth storage layer providing access to stores.
  • hashConfig: Argon2id password hashing parameters. If nil, secure OWASP- recommended defaults are used.
  • auditLogger: Interface for logging security events. If nil, a no-op logger is used (events are silently discarded).
  • loginAttemptTracker: Tracks failed login attempts for account lockout. Can be nil if brute force protection is not needed.

The function ensures all nil inputs are replaced with safe defaults, so it will never return a partially-configured service.

func (*AuthService) GetAuthConfig

func (as *AuthService) GetAuthConfig() *AuthConfig

GetAuthConfig returns the authentication configuration used by this service. This includes session settings, password policy, and user field filtering.

func (*AuthService) GetUserFieldsConfig

func (as *AuthService) GetUserFieldsConfig() *UserFieldsConfig

GetUserFieldsConfig returns the user fields configuration which controls which user fields are included or excluded in API responses.

Returns nil if not configured, meaning all fields are included by default.

type CookieManager

type CookieManager struct {
	// contains filtered or unexported fields
}

CookieManager provides centralized cookie management for Aegis sessions.

This manager encapsulates cookie security best practices:

  • HTTPOnly: Prevents JavaScript access (XSS protection)
  • Secure: Requires HTTPS in production (prevents MITM attacks)
  • SameSite: Prevents CSRF attacks (Lax, Strict, or None)
  • Configurable domain: Supports subdomain sharing
  • Configurable path: Limits cookie scope

The CookieManager is created by SessionService and uses settings from SessionConfig.CookieSettings. All session cookies are managed through this abstraction for consistency.

Cookie Security Best Practices:

  • Always enable HTTPOnly (prevents XSS from stealing cookies)
  • Always enable Secure in production (requires HTTPS)
  • Use SameSite=Lax for general APIs, Strict for sensitive operations
  • Use SameSite=None only when needed for cross-site requests (requires Secure=true)

Example:

cm := core.NewCookieManager(sessionConfig)
cm.SetSessionCookie(w, sessionToken) // Sets with configured security
token, err := cm.GetSessionCookie(r) // Reads session cookie
cm.ClearSessionCookie(w) // Deletes the session cookie

func NewCookieManager

func NewCookieManager(cfg *SessionConfig) *CookieManager

NewCookieManager creates a new CookieManager with the given configuration.

If cfg is nil, uses DefaultSessionConfig() with secure defaults.

The CookieManager will use the settings from cfg.CookieSettings for all cookie operations (HTTPOnly, Secure, SameSite, Domain, Path, Name).

func (*CookieManager) ClearSessionCookie

func (cm *CookieManager) ClearSessionCookie(w http.ResponseWriter)

ClearSessionCookie deletes the session cookie by setting MaxAge to -1.

This is called during logout to invalidate the client-side session. The server-side session is deleted separately via SessionService.DeleteSession.

Note: Even after clearing the cookie, the session token remains valid in the database until DeleteSession is called or the session expires naturally.

Example:

sessionService.DeleteSession(ctx, sessionID) // Server-side cleanup
cookieManager.ClearSessionCookie(w) // Client-side cleanup

func (*CookieManager) GetConfig

func (cm *CookieManager) GetConfig() *SessionConfig

GetConfig returns the underlying SessionConfig used by this CookieManager. Useful for inspecting current cookie settings.

func (*CookieManager) GetCookie

func (cm *CookieManager) GetCookie(r *http.Request, name string) (string, error)

GetCookie retrieves a cookie value by name from the HTTP request.

Returns an AuthError if:

  • Cookie doesn't exist (AuthErrorCodeUnauthorized)
  • Reading the cookie fails (AuthErrorCodeInternal)

Example:

csrfToken, err := cm.GetCookie(r, "csrf_token")
if err != nil {
	// Cookie missing or error
}

func (*CookieManager) GetSessionCookie

func (cm *CookieManager) GetSessionCookie(r *http.Request) (string, error)

GetSessionCookie retrieves the session token from the configured session cookie.

This is the primary method used by AuthMiddleware to extract the session token for authentication. The cookie name is determined by GetSessionCookieName().

Returns an AuthError if the cookie is missing or cannot be read.

Example:

token, err := cm.GetSessionCookie(r)
if err != nil {
	// User not authenticated via cookie
}

func (*CookieManager) GetSessionCookieName

func (cm *CookieManager) GetSessionCookieName() string

GetSessionCookieName returns the configured session cookie name. Returns DefaultCookieName ("aegis_session") if not explicitly configured.

func (*CookieManager) SetCookie

func (cm *CookieManager) SetCookie(w http.ResponseWriter, name, value string, maxAge time.Duration)

SetCookie sets a cookie with the configured security defaults.

This is a convenience method that applies CookieSettings from the config:

  • Domain from config.CookieSettings.Domain
  • Secure from config.CookieSettings.Secure
  • HTTPOnly from config.CookieSettings.HTTPOnly
  • SameSite from config.CookieSettings.SameSite
  • Path is always "/" (DefaultCookiePath)

Parameters:

  • name: Cookie name
  • value: Cookie value
  • maxAge: Cookie lifetime (0 for session cookies, negative to delete)

Example:

cm.SetCookie(w, "custom_data", "value", 24*time.Hour)

func (*CookieManager) SetCustomCookie

func (cm *CookieManager) SetCustomCookie(w http.ResponseWriter, opts CookieOptions)

SetCustomCookie sets a custom cookie with fine-grained control over all options.

This allows overriding specific cookie properties while still benefiting from config defaults for unspecified fields:

  • SameSite: If zero, uses config.CookieSettings.SameSite
  • Domain: If empty, uses config.CookieSettings.Domain

Use this for plugin-specific cookies (CSRF tokens, OAuth state, etc.) that need different settings than the main session cookie.

Example:

cm.SetCustomCookie(w, core.CookieOptions{
	Name:     "csrf_token",
	Value:    csrfToken,
	Path:     "/",
	MaxAge:   3600, // 1 hour
	HTTPOnly: true,
	Secure:   true,
	SameSite: http.SameSiteStrictMode,
})

func (*CookieManager) SetSessionCookie

func (cm *CookieManager) SetSessionCookie(w http.ResponseWriter, token string)

SetSessionCookie sets the session cookie with configured security settings.

This is the primary method for creating session cookies after successful login. The cookie expires according to config.SessionExpiry.

Security settings applied:

  • HTTPOnly: true (prevents XSS)
  • Secure: from config (true in production)
  • SameSite: from config (Lax/Strict for CSRF protection)
  • Domain: from config (supports subdomain sharing)

Parameters:

  • token: The session token (random value from CreateSession)

Example:

session, _ := sessionService.CreateSession(ctx, user)
cookieManager.SetSessionCookie(w, session.Token)

type CookieOptions

type CookieOptions struct {
	// Name is the cookie name
	Name string

	// Value is the cookie value (should not contain sensitive data unless encrypted)
	Value string

	// Path restricts the cookie to specific URL paths (default: "/")
	Path string

	// MaxAge is the cookie lifetime in seconds
	// Positive: Persistent cookie (survives browser restart)
	// Zero: Session cookie (deleted when browser closes)
	// Negative: Delete cookie immediately
	MaxAge int

	// Domain restricts the cookie to a specific domain or subdomain
	// Empty: Current domain only
	// ".example.com": All subdomains of example.com
	Domain string

	// Secure requires HTTPS for cookie transmission
	// Always true in production, can be false in local development
	Secure bool

	// HTTPOnly prevents JavaScript access to the cookie
	// Always true for session cookies to prevent XSS attacks
	HTTPOnly bool

	// SameSite controls cross-site cookie behavior (CSRF protection)
	// SameSiteLaxMode: Cookies sent with top-level navigation (default, good balance)
	// SameSiteStrictMode: Cookies never sent cross-site (maximum security)
	// SameSiteNoneMode: Cookies always sent (requires Secure=true, needed for OAuth flows)
	SameSite http.SameSite
}

CookieOptions defines options for setting a custom cookie. Used with SetCustomCookie for fine-grained control over cookie properties.

type CookieSettings

type CookieSettings struct {
	// Name is the cookie name (default: "aegis_session")
	Name string

	// Domain controls which domains can access the cookie.
	// Empty: Current domain only
	// ".example.com": All subdomains of example.com
	Domain string

	// Secure requires HTTPS for cookie transmission.
	// Always true in production. Can be false for local development.
	Secure bool

	// HTTPOnly prevents JavaScript from accessing the cookie.
	// Always true for session cookies (XSS protection).
	HTTPOnly bool

	// SameSite controls cross-site cookie behavior (CSRF protection).
	// Options:
	//   - "Strict": Cookie never sent in cross-site requests
	//   - "Lax": Cookie sent on top-level navigation (default, recommended)
	//   - "None": Cookie always sent (requires Secure=true)
	SameSite string
}

CookieSettings defines HTTP cookie configuration for session tokens.

Security best practices:

  • Always set HTTPOnly=true (prevents JavaScript access)
  • Set Secure=true in production (HTTPS only)
  • Use SameSite="Lax" or "Strict" (CSRF protection)
  • Set Domain to your domain for subdomain sharing

type EmailPasswordHandlers

type EmailPasswordHandlers struct {
	// contains filtered or unexported fields
}

EmailPasswordHandlers provides HTTP handlers and programmatic functions for traditional email+password authentication.

This handler set implements the classic username/password authentication flow:

  • Registration: Create a new user with email+password
  • Login: Authenticate existing user with credentials

HTTP handlers are private (lowercase) and automatically mounted. For programmatic use without HTTP, use the public methods:

  • Login(ctx, email, password)
  • Register(ctx, name, email, password)

IP address and user agent are automatically extracted from the request context (populated by AegisContextMiddleware). For non-HTTP usage, populate the context with WithRequestMeta.

func NewEmailPasswordHandlers

func NewEmailPasswordHandlers(authService *AuthService) *EmailPasswordHandlers

NewEmailPasswordHandlers creates a new set of email+password authentication handlers.

func (*EmailPasswordHandlers) Login

func (h *EmailPasswordHandlers) Login(ctx context.Context, email, password string) (*LoginResult, error)

Login authenticates a user with email and password programmatically. IP address and user agent are automatically extracted from the request context.

func (*EmailPasswordHandlers) Register

func (h *EmailPasswordHandlers) Register(ctx context.Context, name, email, password string) (*RegisterResult, error)

Register registers a new user with email and password programmatically. IP address and user agent are automatically extracted from the request context.

type EnrichedUser

type EnrichedUser struct {
	*auth.User

	// Extensions holds additional fields from plugins.
	// Keys are simple field names: "role", "verified", "organizations", etc.
	// These are flattened into the JSON response as top-level fields.
	Extensions map[string]any `json:"-"` // Excluded from default marshal, handled in MarshalJSON
	// contains filtered or unexported fields
}

EnrichedUser wraps the core auth.User with plugin-specific extensions.

This is a key extensibility mechanism in Aegis that allows plugins to augment the base user model with additional fields without modifying the core schema. Plugin data is stored in the Extensions map and automatically merged into JSON responses.

Common use cases:

  • Admin plugin adds: "role", "permissions"
  • Organizations plugin adds: "organizations", "currentOrg"
  • JWT plugin adds: "claims", "tokenExp"
  • Email verification plugin adds: "emailVerified"

Extension keys should be simple field names (not nested paths). The MarshalJSON implementation flattens extensions as top-level fields in API responses.

Example plugin usage:

enriched := core.GetEnrichedUser(ctx)
enriched.Set("role", "admin")
enriched.Set("emailVerified", true)
enriched.Set("organizations", []string{"org1", "org2"})

Example API response:

{
  "id": "01HXYZ...",
  "email": "user@example.com",
  "name": "John Doe",
  "role": "admin",               // From extension
  "emailVerified": true,          // From extension
  "organizations": ["org1", "org2"] // From extension
}

Thread safety: All methods are safe for concurrent use via internal mutex.

func GetEnrichedUser

func GetEnrichedUser(ctx context.Context) *EnrichedUser

GetEnrichedUser extracts the enriched user from context. This includes all plugin extensions (admin role, jwt claims, org memberships, etc.) Returns nil if no authenticated user or enriched user not set.

func MustGetEnrichedUser

func MustGetEnrichedUser(ctx context.Context) *EnrichedUser

MustGetEnrichedUser extracts the enriched user, panicking if not found. Use only in handlers where authentication is guaranteed.

func NewEnrichedUser

func NewEnrichedUser(user *auth.User) *EnrichedUser

NewEnrichedUser creates an EnrichedUser wrapping a core User. The Extensions map is initialized empty, ready for plugins to populate.

func (*EnrichedUser) Get

func (eu *EnrichedUser) Get(key string) any

Get retrieves an extension value by key. Returns nil if the key doesn't exist.

For type-safe access, prefer the typed getters (GetString, GetBool, etc.).

func (*EnrichedUser) GetBool

func (eu *EnrichedUser) GetBool(key string) bool

GetBool retrieves a bool extension value. Returns false if the key doesn't exist or value is not a bool.

func (*EnrichedUser) GetMap

func (eu *EnrichedUser) GetMap(key string) map[string]any

GetMap retrieves a map extension value. Returns nil if the key doesn't exist or value is not a map.

func (*EnrichedUser) GetString

func (eu *EnrichedUser) GetString(key string) string

GetString retrieves a string extension value. Returns empty string if the key doesn't exist or value is not a string.

func (*EnrichedUser) GetStringSlice

func (eu *EnrichedUser) GetStringSlice(key string) []string

GetStringSlice retrieves a string slice extension value. Returns nil if the key doesn't exist or value is not a []string.

func (*EnrichedUser) Has

func (eu *EnrichedUser) Has(key string) bool

Has checks if an extension key exists. Returns true even if the value is nil.

func (*EnrichedUser) Keys

func (eu *EnrichedUser) Keys() []string

Keys returns all extension keys currently set. Useful for debugging or iterating over all extensions.

func (*EnrichedUser) MarshalJSON

func (eu *EnrichedUser) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for API responses. Extensions are flattened as top-level fields in the JSON output.

func (*EnrichedUser) Set

func (eu *EnrichedUser) Set(key string, value any)

Set adds or updates an extension field.

This is typically called by plugins during request processing to add their data to the user context. Key should be a simple field name that will become a top-level field in JSON responses.

Thread-safe for concurrent plugin access.

func (*EnrichedUser) ToAPIResponse

func (eu *EnrichedUser) ToAPIResponse() map[string]any

ToAPIResponse returns a map suitable for JSON API responses. Extensions are flattened as top-level fields.

func (*EnrichedUser) ToAPIResponseFiltered

func (eu *EnrichedUser) ToAPIResponseFiltered(config *UserFieldsConfig) map[string]any

ToAPIResponseFiltered returns a map suitable for JSON API responses, optionally filtering extension fields based on the provided config. If config is nil, all fields are included.

type HTTPLogger

type HTTPLogger interface {
	Error(msg string, keysAndValues ...any)
}

HTTPLogger is an optional interface for logging HTTP helper errors. This is a subset of structured logging interfaces (zap, logrus, slog).

type IDGeneratorFunc

type IDGeneratorFunc func() string

IDGeneratorFunc is a function type for custom ID generation. Implement this to use your own ID generation algorithm (KSUIDs, nanoid, Snowflake, etc.).

type IDStrategy

type IDStrategy string

IDStrategy defines the algorithm used for generating unique identifiers.

Aegis supports multiple ID generation strategies to accommodate different use cases and preferences.

const (
	// IDStrategyULID uses ULID (Universally Unique Lexicographically Sortable Identifier).
	// This is the DEFAULT strategy.
	//
	// Benefits:
	//   - Sortable: IDs are ordered by creation time
	//   - Compact: 26 characters (vs 36 for UUID)
	//   - No configuration needed: Works immediately
	//   - Collision resistant: 80 bits of randomness
	//   - Database friendly: Efficient indexing due to sortability
	//
	// Format: 01ARZ3NDEKTSV4RRFFQ69G5FAV (26 characters)
	// Structure: 10-byte timestamp + 16-byte randomness
	//
	// Best for: Most use cases, especially when you need sortable IDs
	IDStrategyULID IDStrategy = "ulid"

	// IDStrategyUUID uses UUID v4 (random UUIDs).
	//
	// Benefits:
	//   - Standard format: Widely recognized and supported
	//   - Maximum randomness: 122 bits of entropy
	//   - Collision resistant: Extremely low probability of collisions
	//
	// Drawbacks:
	//   - Not sortable: IDs are random, not time-ordered
	//   - Longer: 36 characters with hyphens
	//   - Database indexing: Less efficient than ULIDs
	//
	// Format: 550e8400-e29b-41d4-a716-446655440000 (36 characters)
	//
	// Best for: When you need standard UUID format or maximum randomness
	IDStrategyUUID IDStrategy = "uuid"

	// IDStrategyCustom uses a user-provided custom ID generation function.
	//
	// Use this when you need:
	//   - KSUID (K-Sortable Unique Identifier)
	//   - Snowflake IDs (Twitter's distributed ID generation)
	//   - Nanoid (shorter, URL-safe IDs)
	//   - Database-generated IDs (SERIAL, AUTO_INCREMENT)
	//   - Custom formatting or business logic
	//
	// Set the custom generator with:
	//   core.SetCustomIDGenerator(func() string { return myIDGenerator() })
	//
	// Best for: Specialized requirements or existing ID systems
	IDStrategyCustom IDStrategy = "custom"
)

func GetIDStrategy

func GetIDStrategy() IDStrategy

GetIDStrategy returns the currently active ID generation strategy.

Useful for logging or debugging to verify which strategy is in use.

type KeyManager

type KeyManager interface {
	// Get retrieves a value by key
	// Returns error if key doesn't exist or retrieval fails
	Get(ctx context.Context, key string) ([]byte, error)

	// Set stores a value with optional expiry
	// expiry=0 means no expiration (permanent storage)
	Set(ctx context.Context, key string, value []byte, expiry time.Duration) error

	// Delete removes a value by key
	Delete(ctx context.Context, key string) error
}

KeyManager defines a general-purpose key-value storage interface.

This interface is used by plugins for storing temporary data:

  • OAuth state tokens (OAuth plugin)
  • CSRF tokens (CSRF protection)
  • Email verification codes (EmailOTP plugin)
  • SMS verification codes (SMS plugin)
  • JWT refresh token blacklists (JWT plugin)

Implementations:

  • StaticKeyManager: In-memory storage (development/testing)
  • RedisKeyManager: Redis-backed storage (production)

Unlike SessionService which uses Redis for session caching, KeyManager is a general-purpose abstraction that plugins can use for any temporary data.

Example (OAuth state storage):

keyManager.Set(ctx, "oauth:state:"+state, []byte(redirectURL), 10*time.Minute)
redirectURL, _ := keyManager.Get(ctx, "oauth:state:"+state)
keyManager.Delete(ctx, "oauth:state:"+state)

type LoggerAuditLogger

type LoggerAuditLogger struct {
	// contains filtered or unexported fields
}

LoggerAuditLogger implements AuditLogger using a structured logger interface. This adapter allows using any logger (zap, logrus, slog) that implements the Info/Error/Debug methods.

func NewLoggerAuditLogger

func NewLoggerAuditLogger(logger interface {
	Info(msg string, keysAndValues ...any)
	Error(msg string, keysAndValues ...any)
	Debug(msg string, keysAndValues ...any)
}) *LoggerAuditLogger

NewLoggerAuditLogger creates an audit logger that writes to a structured logger.

func (*LoggerAuditLogger) LogAuthEvent

func (l *LoggerAuditLogger) LogAuthEvent(ctx context.Context, eventType AuditEventType, userID string, success bool, details map[string]any) error

LogAuthEvent implements AuditLogger. IP address and user agent are extracted from the request context.

func (*LoggerAuditLogger) LogEvent

func (l *LoggerAuditLogger) LogEvent(_ context.Context, event *AuditEvent) error

LogEvent implements AuditLogger.

type LoginAttemptConfig

type LoginAttemptConfig struct {
	// MaxAttempts is the threshold before triggering account lockout.
	// Example: 5 means lock after the 5th failed attempt.
	MaxAttempts int

	// LockoutDuration is how long the account remains locked.
	// After this duration, the user can attempt to login again.
	LockoutDuration time.Duration

	// AttemptWindow is the time window for counting failed attempts.
	// Attempts older than this window don't count toward the limit.
	AttemptWindow time.Duration
}

LoginAttemptConfig configures login attempt tracking behavior.

func DefaultLoginAttemptConfig

func DefaultLoginAttemptConfig() *LoginAttemptConfig

DefaultLoginAttemptConfig returns sensible defaults

type LoginAttemptTracker

type LoginAttemptTracker struct {
	// contains filtered or unexported fields
}

LoginAttemptTracker tracks failed login attempts for account lockout protection.

This prevents brute force attacks by temporarily locking accounts after too many failed login attempts. Like RateLimiter, it supports both Redis (distributed) and in-memory (single-instance) backends.

func NewLoginAttemptTracker

func NewLoginAttemptTracker(config *LoginAttemptConfig, redisClient *redis.Client) *LoginAttemptTracker

NewLoginAttemptTracker creates a new login attempt tracker

func (*LoginAttemptTracker) ClearAttempts

func (lat *LoginAttemptTracker) ClearAttempts(ctx context.Context, identifier string) error

ClearAttempts clears failed attempts for an identifier (on successful login)

func (*LoginAttemptTracker) IsLockedOut

func (lat *LoginAttemptTracker) IsLockedOut(ctx context.Context, identifier string) (bool, time.Duration, error)

IsLockedOut checks if an identifier is locked out

func (*LoginAttemptTracker) RecordFailedAttempt

func (lat *LoginAttemptTracker) RecordFailedAttempt(ctx context.Context, identifier string) (int, bool, error)

RecordFailedAttempt records a failed login attempt

func (*LoginAttemptTracker) Stop

func (lat *LoginAttemptTracker) Stop()

Stop stops the tracker cleanup goroutine

type LoginRequest

type LoginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

LoginRequest represents the JSON payload for email+password login.

type LoginResult

type LoginResult struct {
	// User is the authenticated user
	User auth.User
	// Session is the newly created session
	Session *auth.Session
	// Token is the session token
	Token string
}

LoginResult contains the result of an email+password login.

type NoOpAuditLogger

type NoOpAuditLogger struct{}

NoOpAuditLogger is a no-op implementation that discards all events. Useful for testing or when audit logging is not required.

func (*NoOpAuditLogger) LogAuthEvent

func (n *NoOpAuditLogger) LogAuthEvent(_ context.Context, _ AuditEventType, _ string, _ bool, _ map[string]any) error

LogAuthEvent implements AuditLogger.

func (*NoOpAuditLogger) LogEvent

func (n *NoOpAuditLogger) LogEvent(_ context.Context, _ *AuditEvent) error

LogEvent implements AuditLogger.

type PaginationParams

type PaginationParams struct {
	// Page is the 1-based page number (default: 1)
	Page int

	// Limit is the number of items per page (default: 20, max: 100)
	Limit int

	// Offset is the calculated skip offset for database queries
	// Automatically calculated as (Page-1) * Limit
	Offset int
}

PaginationParams holds parsed and validated pagination parameters. Used with ParsePagination to extract pagination from query strings.

func ParsePagination

func ParsePagination(r *http.Request) PaginationParams

ParsePagination extracts and validates pagination parameters from request query string.

Query parameters:

  • page: Page number (1-based, default: 1)
  • limit: Items per page (default: 20, max: 100)

Invalid values are replaced with defaults:

  • page < 1 becomes 1
  • limit < 1 or limit > 100 becomes 20

Example:

params := core.ParsePagination(r) // ?page=2&limit=50
users, _ := userStore.List(ctx, params.Offset, params.Limit)

type PasswordHasherConfig

type PasswordHasherConfig struct {
	// Argon2Time is the number of iterations (time cost)
	Argon2Time uint32

	// Argon2Memory is the memory cost in KiB (e.g., 65536 = 64MB)
	Argon2Memory uint32

	// Argon2Threads is the degree of parallelism (typically 4)
	Argon2Threads uint8

	// Argon2KeyLength is the derived key length in bytes (typically 32)
	Argon2KeyLength uint32
}

PasswordHasherConfig defines Argon2id parameters for password hashing.

Argon2id is memory-hard and resistant to GPU/ASIC attacks. Higher values increase security at the cost of CPU/memory usage during login.

OWASP 2024 recommendations:

  • Time: 1-3 iterations
  • Memory: 64MB-256MB (64*1024 - 256*1024 KiB)
  • Threads: Match CPU cores (typically 4)
  • KeyLength: 32 bytes (256 bits)

For high-security applications, increase Memory to 256MB+ and Time to 3+. For resource-constrained environments, keep defaults but monitor load.

func DefaultPasswordHasherConfig

func DefaultPasswordHasherConfig() *PasswordHasherConfig

DefaultPasswordHasherConfig returns default password hashing configuration.

type PasswordPolicyConfig

type PasswordPolicyConfig struct {
	// MinLength is minimum password length (default: 8, NIST minimum)
	MinLength int

	// RequireUpper requires at least one uppercase letter (default: true)
	RequireUpper bool

	// RequireLower requires at least one lowercase letter (default: true)
	RequireLower bool

	// RequireDigit requires at least one numeric digit (default: true)
	RequireDigit bool

	// RequireSpecial requires at least one special character (default: false)
	// Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?
	RequireSpecial bool

	// MaxLength caps password length to prevent DoS (default: 128, 0 = unlimited)
	// Very long passwords can cause excessive CPU usage during hashing.
	MaxLength int
}

PasswordPolicyConfig defines password validation rules.

Modern password policy recommendations (NIST/OWASP 2024):

  • Require minimum length (8+ characters)
  • Optionally require character diversity (mixed case, digits, symbols)
  • Don't require forced expiration or rotation
  • Check against breached password databases

Note: Overly strict policies can lead to weaker passwords (users write them down, use predictable patterns, etc.). Balance security with usability.

func DefaultPasswordPolicyConfig

func DefaultPasswordPolicyConfig() *PasswordPolicyConfig

DefaultPasswordPolicyConfig returns default password policy configuration

type PathParamFunc

type PathParamFunc func(r *http.Request, name string) string

PathParamFunc is a function type for extracting path parameters from requests. This allows different routers to provide their own implementation.

type PluginData

type PluginData struct {
	// contains filtered or unexported fields
}

PluginData is a thread-safe store for plugin-specific context data. Plugins can store and retrieve their own data without key collisions.

func GetPluginData

func GetPluginData(ctx context.Context) *PluginData

GetPluginData extracts the plugin data store from the context. Returns nil if not initialized. Plugins should check for nil.

func NewPluginData

func NewPluginData() *PluginData

NewPluginData creates a new plugin data store

func (*PluginData) Delete

func (pd *PluginData) Delete(key string)

Delete removes a key from the plugin data store.

func (*PluginData) Get

func (pd *PluginData) Get(key string) any

Get retrieves a value from the plugin data store. Returns nil if the key doesn't exist.

func (*PluginData) GetBool

func (pd *PluginData) GetBool(key string) bool

GetBool retrieves a bool value, returning false if not found or wrong type.

func (*PluginData) GetString

func (pd *PluginData) GetString(key string) string

GetString retrieves a string value, returning empty string if not found or wrong type.

func (*PluginData) Has

func (pd *PluginData) Has(key string) bool

Has checks if a key exists in the plugin data store.

func (*PluginData) Keys

func (pd *PluginData) Keys() []string

Keys returns all keys in the plugin data store.

func (*PluginData) Set

func (pd *PluginData) Set(key string, value any)

Set stores a value for a plugin. The key should be namespaced by plugin name. Example: pluginData.Set("jwt:token_type", "access")

type RateLimitConfig

type RateLimitConfig struct {
	// RequestsPerWindow is the maximum number of requests allowed per time window.
	// After exceeding this, requests will receive HTTP 429 Too Many Requests.
	RequestsPerWindow int

	// WindowDuration is the time window for counting requests.
	// Example: 100 requests per 1 minute means users can make 100 requests,
	// then must wait up to 1 minute before the counter resets.
	WindowDuration time.Duration

	// KeyPrefix is the Redis key prefix for rate limit counters.
	// This prevents collisions with other Redis data.
	KeyPrefix string

	// ByIP enables rate limiting by client IP address.
	// Useful for preventing abuse from specific sources.
	ByIP bool

	// ByUser enables rate limiting by authenticated user ID.
	// Requires that requests are authenticated. Unauthenticated requests
	// won't be rate limited by user.
	ByUser bool

	// ExcludePaths are URL paths exempt from rate limiting.
	// Example: ["/health", "/metrics"] for monitoring endpoints.
	ExcludePaths []string
}

RateLimitConfig configures rate limiting behavior. Rate limits can be applied by IP address, user ID, or both.

func AuthRateLimitConfig

func AuthRateLimitConfig() *RateLimitConfig

AuthRateLimitConfig returns stricter limits for authentication endpoints. Authentication endpoints (login, signup) should have tighter limits to prevent brute force attacks and credential stuffing.

func DefaultRateLimitConfig

func DefaultRateLimitConfig() *RateLimitConfig

DefaultRateLimitConfig returns sensible defaults for general API endpoints. These limits are suitable for most read-heavy applications.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter provides distributed rate limiting functionality.

It uses Redis for distributed rate limiting across multiple application instances, with an in-memory fallback for single-instance deployments. The sliding window algorithm prevents bursty traffic from overwhelming the system.

The limiter is safe for concurrent use and should be shared across HTTP handlers.

func NewRateLimiter

func NewRateLimiter(config *RateLimitConfig, redisClient *redis.Client, auditLogger AuditLogger) *RateLimiter

NewRateLimiter creates a new rate limiter with the specified configuration.

Parameters:

  • config: Rate limiting configuration. If nil, uses defaults.
  • redisClient: Redis client for distributed rate limiting. If nil, uses in-memory fallback (only suitable for single-instance deployments).
  • auditLogger: Logger for recording rate limit violations. If nil, uses a no-op logger.

The returned RateLimiter is safe for concurrent use. For multi-instance deployments, a Redis client must be provided to enforce limits across all instances.

func (*RateLimiter) Allow

func (rl *RateLimiter) Allow(ctx context.Context, key string) (bool, int, error)

Allow checks if a request should be allowed

func (*RateLimiter) Stop

func (rl *RateLimiter) Stop()

Stop stops the rate limiter cleanup goroutine

type RedisConfig

type RedisConfig struct {
	// Host is the Redis server hostname or IP (e.g., "localhost", "redis.example.com")
	Host string

	// Port is the Redis server port (default: 6379)
	Port int

	// Password for Redis authentication (empty if no auth required)
	Password string

	// DB is the Redis database number (0-15, default: 0)
	DB int
}

RedisConfig defines Redis connection parameters. Redis is used for session caching and distributed rate limiting.

type RedisKeyManager

type RedisKeyManager struct {
	// contains filtered or unexported fields
}

RedisKeyManager provides Redis-backed key-value storage with automatic expiration.

This is the recommended KeyManager implementation for production because:

  • Data persists across server restarts (if Redis is configured for persistence)
  • Shared across multiple server instances
  • Automatic expiry with TTL support
  • High performance with low latency

Use this for:

  • Production deployments
  • Distributed systems with multiple servers
  • Any data that needs automatic expiry (OAuth states, OTP codes, etc.)

Example:

redisClient := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
keyManager := core.NewRedisKeyManager(redisClient)

func NewRedisKeyManager

func NewRedisKeyManager(client *redis.Client) *RedisKeyManager

NewRedisKeyManager creates a new Redis-backed key manager.

The provided Redis client should be already configured and connected. The KeyManager will use the client for all operations.

Example:

redisClient := redis.NewClient(&redis.Options{
	Addr:     "localhost:6379",
	Password: "",
	DB:       0,
})
keyManager := core.NewRedisKeyManager(redisClient)

func (*RedisKeyManager) Delete

func (m *RedisKeyManager) Delete(ctx context.Context, key string) error

Delete removes a value from Redis.

This is idempotent - deleting a non-existent key does nothing and returns no error.

Example:

// Delete OAuth state after use to prevent replay attacks
keyManager.Delete(ctx, "oauth:state:abc123")

func (*RedisKeyManager) Get

func (m *RedisKeyManager) Get(ctx context.Context, key string) ([]byte, error)

Get retrieves a value from Redis.

Returns an error if:

  • Key doesn't exist (redis.Nil → AuthErrorCodeInternal)
  • Redis operation fails (network error, etc.)

Example:

value, err := keyManager.Get(ctx, "oauth:state:abc123")
if err != nil {
	// Key doesn't exist or Redis error
}

func (*RedisKeyManager) Set

func (m *RedisKeyManager) Set(ctx context.Context, key string, value []byte, expiry time.Duration) error

Set stores a value in Redis with optional automatic expiration.

The expiry parameter controls key lifetime:

  • expiry > 0: Key expires after the specified duration
  • expiry = 0: Key never expires (persists indefinitely)

Redis will automatically delete expired keys using its eviction policy.

Example:

// OAuth state valid for 10 minutes
keyManager.Set(ctx, "oauth:state:abc123", stateData, 10*time.Minute)

// Email verification code valid for 1 hour
keyManager.Set(ctx, "email:verify:user@example.com", []byte("123456"), time.Hour)

type RegisterRequest

type RegisterRequest struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

RegisterRequest represents the JSON payload for email+password registration.

type RegisterResult

type RegisterResult struct {
	// User is the newly created user
	User auth.User
	// Session is the newly created session (auto-login)
	Session *auth.Session
	// Token is the session token
	Token string
}

RegisterResult contains the result of an email+password registration.

type RequestBodyMeta

type RequestBodyMeta struct {
	// Description explains what the request body contains
	Description string

	// Required indicates if a request body must be provided
	Required bool

	// Schema is either:
	//   - A string with the schema name (e.g., "CreateUserRequest")
	//   - An inline schema definition (struct or map)
	Schema any
}

RequestBodyMeta describes the expected request body for an API endpoint. Used by the OpenAPI plugin for automatic documentation generation.

type RequestMeta

type RequestMeta struct {
	// RequestID is a unique identifier for this request (for tracing)
	RequestID string
	// IPAddress is the client's IP address
	IPAddress string
	// UserAgent is the client's User-Agent header
	UserAgent string
	// Method is the HTTP method (GET, POST, etc.)
	Method string
	// Path is the request path
	Path string
}

RequestMeta contains metadata about the current request. This is useful for audit logging, rate limiting, and plugin access.

func GetRequestMeta

func GetRequestMeta(ctx context.Context) *RequestMeta

GetRequestMeta extracts the request metadata from the context. Returns nil if not set.

type Response

type Response struct {
	// Success indicates if the request completed successfully
	Success bool `json:"success"`

	// Message contains a human-readable success message (optional)
	Message string `json:"message,omitempty"`

	// Error contains a human-readable error message (optional, Success=false)
	Error string `json:"error,omitempty"`

	// Data contains the response payload (optional, Success=true)
	Data any `json:"data,omitempty"`
}

Response represents a standard JSON API response structure. This provides a consistent format across all Aegis endpoints.

type ResponseMeta

type ResponseMeta struct {
	// Description explains what this response represents
	Description string

	// Schema is either:
	//   - A string with the schema name (e.g., "User", "Error")
	//   - An inline schema definition (struct or map)
	Schema any
}

ResponseMeta describes a possible response for an API endpoint. Used by the OpenAPI plugin for automatic documentation generation.

type RouteMetadata

type RouteMetadata struct {
	// Method is the HTTP method (GET, POST, PUT, DELETE, PATCH, etc.)
	Method string

	// Path is the route path (e.g., "/auth/logout", "/users/:id")
	Path string

	// Summary is a short one-line description of the endpoint
	Summary string

	// Description is a detailed explanation of what the endpoint does
	Description string

	// Tags are used for grouping operations in documentation (e.g., ["Auth", "Users"])
	Tags []string

	// Protected indicates if the endpoint requires authentication
	Protected bool

	// RequestBody describes the expected request body (optional)
	RequestBody *RequestBodyMeta

	// Responses maps HTTP status codes to response descriptions
	// Example: {"200": {...}, "401": {...}, "500": {...}}
	Responses map[string]*ResponseMeta
}

RouteMetadata contains OpenAPI documentation metadata for a route.

This metadata enables automatic API documentation generation via the OpenAPI plugin. Developers annotate routes with this metadata, and the OpenAPI spec is automatically generated.

Example:

metadata := &core.RouteMetadata{
	Method:      "POST",
	Path:        "/auth/login",
	Summary:     "Authenticate user",
	Description: "Login with email and password",
	Tags:        []string{"Authentication"},
	Protected:   false,
	RequestBody: &core.RequestBodyMeta{
		Description: "Login credentials",
		Required:    true,
		Schema:      "LoginRequest",
	},
	Responses: map[string]*core.ResponseMeta{
		"200": {Description: "Successful login", Schema: "LoginResponse"},
		"401": {Description: "Invalid credentials", Schema: "Error"},
	},
}

type SanitizationConfig

type SanitizationConfig struct {
	// MaxLength is the maximum allowed length for sanitized strings (0 = no limit)
	MaxLength int

	// AllowUnicode determines if non-ASCII Unicode characters are permitted
	AllowUnicode bool

	// StripHTML removes all HTML tags from input
	StripHTML bool

	// NormalizeWhitespace collapses multiple spaces into single spaces
	NormalizeWhitespace bool

	// TrimWhitespace removes leading and trailing whitespace
	TrimWhitespace bool
}

SanitizationConfig controls the behavior of sanitization functions.

func DefaultSanitizationConfig

func DefaultSanitizationConfig() *SanitizationConfig

DefaultSanitizationConfig returns a secure default configuration.

Defaults:

  • MaxLength: 1000 characters (prevents DoS via large inputs)
  • AllowUnicode: true (supports international users)
  • StripHTML: true (prevents XSS)
  • NormalizeWhitespace: true (cleans up formatting)
  • TrimWhitespace: true (removes accidental spaces)

type SchemaRequirement

type SchemaRequirement struct {
	// Name is a human-readable identifier for this requirement
	// Example: "Table 'users' exists", "Column 'accounts.password_hash' exists"
	Name string

	// Schema is the database schema name (optional, for multi-schema databases)
	Schema string

	// Table is the table name being validated (optional, for documentation)
	Table string

	// Query is the SQL query to execute
	// Should succeed without error if the requirement is met
	// Example: "SELECT 1 FROM users WHERE 1=0"
	Query string

	// Description explains what the validation failure means
	// This is shown in the error message if the query returns no rows
	// Example: "Table 'users' does not exist or is empty"
	Description string
}

SchemaRequirement defines a single schema validation check.

Each requirement represents one validation rule (table exists, column exists, index exists, etc.). The validator executes the Query and expects it to succeed without error for the requirement to pass.

func SchemaRequirements

func SchemaRequirements() []SchemaRequirement

SchemaRequirements returns the schema requirements for core Aegis tables.

This validates the existence of:

  • Tables: user, accounts, verification, session
  • All required columns in each table

Call this during application startup to ensure the database is properly migrated:

validator := core.NewSchemaValidator(db)
if err := validator.ValidateRequirements(ctx, core.SchemaRequirements()); err != nil {
	log.Fatal("Schema validation failed:", err)
}

func ValidateColumnExists

func ValidateColumnExists(tableName, columnName string) SchemaRequirement

ValidateColumnExists creates a requirement to check if a column exists in a table.

This queries `information_schema.columns` for the given table and column. The actual query generated is:

SELECT column_name FROM information_schema.columns WHERE table_name = '<table>' AND column_name = '<column>' LIMIT 1

Example:

requirement := core.ValidateColumnExists("users", "email")
// Will check: SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'email' LIMIT 1

func ValidateTableExists

func ValidateTableExists(tableName string) SchemaRequirement

ValidateTableExists creates a requirement to check if a table exists.

This queries `information_schema.tables` for the given table name. The actual query generated is:

SELECT table_name FROM information_schema.tables WHERE table_name = '<name>' LIMIT 1

Example:

requirement := core.ValidateTableExists("users")
// Will check: SELECT table_name FROM information_schema.tables WHERE table_name = 'users' LIMIT 1

type SchemaValidator

type SchemaValidator struct {
	// contains filtered or unexported fields
}

SchemaValidator provides database schema validation for Aegis.

This validator helps ensure that the required database tables and columns exist before the application starts. It's used by:

  • Core Aegis (validates user, accounts, session, verification tables)
  • Plugins (each plugin can validate its own schema requirements)

The validator executes SQL queries to check for table/column existence, collecting all validation errors before failing. This provides complete error visibility instead of failing on the first missing table.

Example:

validator := core.NewSchemaValidator(db)
err := validator.ValidateRequirements(ctx, core.SchemaRequirements())
if err != nil {
	log.Fatal("Database schema validation failed:", err)
}

func NewSchemaValidator

func NewSchemaValidator(db *sql.DB) *SchemaValidator

NewSchemaValidator creates a new schema validator for the given database.

The validator will execute queries against this database connection to validate schema requirements.

func (*SchemaValidator) ValidateRequirements

func (v *SchemaValidator) ValidateRequirements(ctx context.Context, requirements []SchemaRequirement) error

ValidateRequirements validates a list of schema requirements.

This method runs all validations and collects ALL errors before returning. This provides complete visibility into schema problems instead of failing on the first error.

Returns nil if all requirements pass, or an error listing all failures.

Example:

requirements := []core.SchemaRequirement{
	core.ValidateTableExists("users"),
	core.ValidateColumnExists("users", "email"),
	core.ValidateColumnExists("users", "password_hash"),
}
err := validator.ValidateRequirements(ctx, requirements)

type SessionConfig

type SessionConfig struct {
	// SessionExpiry is how long session tokens remain valid.
	// After this, users must re-login or use a refresh token.
	// Typical: 24 hours for session, 7 days for refresh
	SessionExpiry time.Duration

	// RefreshExpiry is how long refresh tokens remain valid.
	// Enables "remember me" functionality by allowing new sessions
	// to be created without re-entering credentials.
	RefreshExpiry time.Duration

	// CookieSettings configures HTTP session cookies
	CookieSettings CookieSettings

	// Redis connection for session caching (optional).
	// If nil, sessions are always loaded from database (slower but simpler).
	Redis *RedisConfig
}

SessionConfig defines session and cookie management settings.

func DefaultSessionConfig

func DefaultSessionConfig() *SessionConfig

DefaultSessionConfig returns default session configuration

type SessionModel

type SessionModel interface {
	// GetID returns the unique identifier for this session
	GetID() string

	// SetID assigns a unique identifier to this session
	SetID(string)

	// GetUserID returns the ID of the user this session belongs to
	GetUserID() string

	// SetUserID assigns the owning user's ID
	SetUserID(string)

	// GetToken returns the session authentication token
	GetToken() string

	// SetToken assigns the session authentication token
	SetToken(string)

	// GetRefreshToken returns the refresh token for session renewal
	GetRefreshToken() string

	// SetRefreshToken assigns the refresh token
	SetRefreshToken(string)

	// SetCreatedAt assigns the creation timestamp
	SetCreatedAt(time.Time)

	// GetExpiresAt returns when this session expires
	GetExpiresAt() time.Time

	// SetExpiresAt assigns the session expiration time
	SetExpiresAt(time.Time)

	// SetIPAddress assigns the client IP address for security tracking
	SetIPAddress(string)

	// SetUserAgent assigns the client user agent for security tracking
	SetUserAgent(string)
}

SessionModel defines the required methods for a session model implementation. Sessions track authenticated user activity and enable stateful authentication.

type SessionService

type SessionService struct {
	// contains filtered or unexported fields
}

SessionService manages user session lifecycle including creation, validation, refresh, and invalidation. It provides optional Redis-based caching for high-performance session lookups in high-traffic applications.

Key features:

  • Token-based authentication with access and refresh tokens
  • Optional Redis caching layer for fast session validation
  • Session expiry and refresh token rotation
  • IP address and user agent tracking for security auditing
  • Bulk session invalidation (logout all devices)

The service is safe for concurrent use and should be shared across handlers.

func NewSessionService

func NewSessionService(userStore auth.UserStore, sessionStore auth.SessionStore, cfg *SessionConfig, auditLogger AuditLogger) *SessionService

NewSessionService creates a new session service with optional Redis caching.

Parameters:

  • userStore: Storage for user lookups during session validation
  • sessionStore: Storage for session persistence
  • cfg: Session configuration (expiry, Redis settings). Uses defaults if nil.
  • auditLogger: Logger for security events. Uses no-op if nil.

If cfg.Redis is provided, a Redis client is created for session caching. This significantly improves performance by avoiding database queries for every authenticated request.

func (*SessionService) CreateSession

func (s *SessionService) CreateSession(ctx context.Context, user *auth.User) (*auth.Session, error)

CreateSession creates a new authenticated session for a user.

Generates cryptographically secure random tokens for both session access and refresh tokens. The session is persisted to the database and optionally cached in Redis for fast subsequent lookups.

Parameters:

  • ctx: Request context for cancellation. Must contain RequestMeta (populated by AegisContextMiddleware) for IP address and user agent.
  • user: The authenticated user to create a session for

Returns the created session with populated Token and RefreshToken fields. These tokens should be sent to the client (typically via HTTP-only cookies or Authorization header).

Logs a successful login audit event upon session creation.

func (*SessionService) DeleteSession

func (s *SessionService) DeleteSession(ctx context.Context, token string) error

DeleteSession deletes a session and invalidates cache

func (*SessionService) DeleteUserSessions

func (s *SessionService) DeleteUserSessions(ctx context.Context, userID string) error

DeleteUserSessions deletes all sessions for a user

func (*SessionService) EnableBearerAuth

func (s *SessionService) EnableBearerAuth()

EnableBearerAuth enables Bearer token authentication for sessions.

func (*SessionService) GetConfig

func (s *SessionService) GetConfig() *SessionConfig

GetConfig returns the session configuration.

func (*SessionService) GetCookieManager

func (s *SessionService) GetCookieManager() *CookieManager

GetCookieManager returns the cookie manager.

func (*SessionService) GetRedisClient

func (s *SessionService) GetRedisClient() *redis.Client

GetRedisClient returns the Redis client used for session storage.

func (*SessionService) GetUserSessions

func (s *SessionService) GetUserSessions(ctx context.Context, userID string) ([]*auth.Session, error)

GetUserSessions retrieves all active sessions for a user

func (*SessionService) IsBearerAuthEnabled

func (s *SessionService) IsBearerAuthEnabled() bool

IsBearerAuthEnabled checks if Bearer token authentication is enabled.

func (*SessionService) Logout

func (s *SessionService) Logout(ctx context.Context, token string) error

Logout deletes a session by token (alias for DeleteSession)

func (*SessionService) RefreshSession

func (s *SessionService) RefreshSession(ctx context.Context, refreshToken string) (*auth.Session, error)

RefreshSession refreshes a session using a refresh token

func (*SessionService) RevokeSessionByID

func (s *SessionService) RevokeSessionByID(ctx context.Context, userID, sessionID string) error

RevokeSessionByID revokes a specific session for a user by its ID.

This method verifies that the session belongs to the user before revocation.

Parameters:

  • ctx: Request context
  • userID: The user ID who owns the session
  • sessionID: The session ID to revoke

Returns:

  • error: AuthErrorCodeSessionNotFound if session does not exist or belong to user, or database error.

func (*SessionService) ValidateSession

func (s *SessionService) ValidateSession(ctx context.Context, tokenString string) (*auth.Session, *auth.User, error)

ValidateSession validates a session token and returns the session and user.

The validation flow:

  1. Check Redis cache if available (fast path)
  2. Fall back to database lookup if not cached
  3. Verify session hasn't expired
  4. Load associated user data
  5. Cache the session in Redis for future requests

This method is called on every authenticated request, so caching is critical for performance in production deployments.

Parameters:

  • ctx: Request context for cancellation
  • tokenString: The session token to validate

Returns:

  • *auth.Session: The valid session
  • *auth.User: The user associated with this session
  • error: AuthErrorCodeTokenExpired if expired, AuthErrorCodeSessionInvalid if not found, AuthErrorCodeUserNotFound if user was deleted

type SessionWithUser

type SessionWithUser struct {
	Session *auth.Session `json:"session"`
	User    *EnrichedUser `json:"user"`
}

SessionWithUser combines session and enriched user data for API responses. This is returned by session validation endpoints. The user data includes all extension fields flattened.

func (*SessionWithUser) ToAPIResponse

func (swu *SessionWithUser) ToAPIResponse() map[string]any

ToAPIResponse returns a map suitable for JSON API responses. Session includes all session fields, user includes all extension fields flattened.

func (*SessionWithUser) ToAPIResponseFiltered

func (swu *SessionWithUser) ToAPIResponseFiltered(config *UserFieldsConfig) map[string]any

ToAPIResponseFiltered returns a map with optional user field filtering. Session data is always fully included; only user extension fields are filtered.

type StaticKeyManager

type StaticKeyManager struct {
	// contains filtered or unexported fields
}

StaticKeyManager provides in-memory key-value storage.

WARNING: This is NOT suitable for production use because:

  • Data is lost on server restart
  • Not shared across multiple server instances
  • No persistence or durability

Use this for:

  • Local development and testing
  • Single-server deployments with non-critical data
  • Unit tests that need fast in-memory storage

For production, use RedisKeyManager instead.

Note: Unlike Redis, expiry is NOT supported - all entries persist until manually deleted or the server restarts.

func NewStaticKeyManager

func NewStaticKeyManager() (*StaticKeyManager, error)

NewStaticKeyManager creates a new in-memory key manager.

This manager stores all data in a Go map with no persistence or expiry. Data is lost when the server stops.

Example:

keyManager, _ := core.NewStaticKeyManager()
keyManager.Set(ctx, "key", []byte("value"), 0)

func (*StaticKeyManager) Delete

func (m *StaticKeyManager) Delete(_ context.Context, key string) error

Delete removes a value from in-memory storage. Does nothing if the key doesn't exist.

func (*StaticKeyManager) Get

func (m *StaticKeyManager) Get(_ context.Context, key string) ([]byte, error)

Get retrieves a value from in-memory storage.

Returns an error if the key doesn't exist.

func (*StaticKeyManager) Set

func (m *StaticKeyManager) Set(_ context.Context, key string, value []byte, _ time.Duration) error

Set stores a value in in-memory storage.

WARNING: The expiry parameter is IGNORED by StaticKeyManager. All entries persist until manually deleted or the server restarts.

For automatic expiry support, use RedisKeyManager instead.

type UserFieldsConfig

type UserFieldsConfig struct {
	// Fields is the list of extension field names to include in user responses.
	// Plugins will only add fields that are in this list (if configured).
	// If nil or empty, all plugin fields are included (default behavior).
	Fields []string
}

UserFieldsConfig defines which extension fields plugins should add to EnrichedUser. This allows users to configure what additional data appears in user API responses.

Example configuration:

UserFields: &core.UserFieldsConfig{
    Fields: []string{"role", "permissions", "organizations", "verified"},
}

This produces JSON responses like:

{
    "id": "user_123",
    "email": "user@example.com",
    "role": "admin",
    "permissions": ["read", "write"],
    "organizations": ["org1", "org2"],
    "verified": true
}

func DefaultUserFieldsConfig

func DefaultUserFieldsConfig() *UserFieldsConfig

DefaultUserFieldsConfig returns default user fields configuration. By default, all extension fields are included in responses.

type UserModel

type UserModel interface {
	// GetID returns the unique identifier for this user
	GetID() string

	// SetID assigns a unique identifier to this user
	SetID(string)

	// GetEmail returns the user's email address
	GetEmail() string

	// SetEmail assigns an email address to this user
	SetEmail(string)

	// GetName returns the user's display name
	GetName() string

	// SetName assigns a display name to this user
	SetName(string)

	// SetCreatedAt assigns the creation timestamp
	SetCreatedAt(time.Time)

	// SetUpdatedAt assigns the last modification timestamp
	SetUpdatedAt(time.Time)
}

UserModel defines the required methods for a user model implementation. Any type implementing this interface can be used as a user in the authentication system.

type UserService

type UserService struct {
	// contains filtered or unexported fields
}

UserService provides high-level user management operations. It orchestrates user creation, deletion, and updates while coordinating with AccountStore and SessionStore to maintain data consistency.

Key responsibilities:

  • User CRUD operations
  • Password account creation during user signup
  • Cascading deletion of accounts and sessions
  • Audit logging of user lifecycle events

The service is safe for concurrent use.

func NewUserService

func NewUserService(userStore auth.UserStore, accountStore auth.AccountStore, sessionStore auth.SessionStore, hashConfig *PasswordHasherConfig, authConfig *AuthConfig, auditLogger AuditLogger) *UserService

NewUserService creates a new user service with the specified dependencies.

func (*UserService) CreateUser

func (s *UserService) CreateUser(ctx context.Context, user auth.User, password string) (auth.User, error)

CreateUser creates a new user with a password-based authentication account.

This is the primary method for email/password signup flows. It:

  1. Assigns a unique ID if not provided
  2. Sets creation and update timestamps
  3. Persists the user to storage
  4. Hashes the password using Argon2id
  5. Creates a password-based account linked to the user

The password is hashed with the configured Argon2id parameters before storage. The account is created with provider="credentials" to distinguish it from OAuth accounts.

Parameters:

  • ctx: Request context for cancellation
  • user: User model with email, name, etc. (ID optional)
  • password: Plaintext password to hash and store

Returns the created user. If creation fails, the user account is not created (no partial state).

func (*UserService) CreateUserWithEmail

func (s *UserService) CreateUserWithEmail(ctx context.Context, name, email, password string) (auth.User, error)

CreateUserWithEmail is a convenience method for creating a user with email/password.

This is a simplified wrapper around CreateUser for the common case of email/password signup. It constructs the user model from individual fields and delegates to CreateUser.

Parameters:

  • ctx: Request context
  • name: User's display name
  • email: User's email address (should be unique)
  • password: Plaintext password

Example:

user, err := userService.CreateUserWithEmail(ctx, "John Doe", "john@example.com", "secret123")

func (*UserService) CreateUserWithoutPassword

func (s *UserService) CreateUserWithoutPassword(ctx context.Context, user auth.User) (auth.User, error)

CreateUserWithoutPassword creates a new user without any authentication account.

This is used for OAuth-only users or when accounts will be created separately. Common scenarios:

  • OAuth signup (Google, GitHub, etc.) where password is not needed
  • Admin-created users where credentials are set later
  • Service accounts or system users

Note: The user won't be able to log in with email/password until a password account is created separately.

Parameters:

  • ctx: Request context
  • user: User model (ID will be generated if not provided)

func (*UserService) DeleteUser

func (s *UserService) DeleteUser(ctx context.Context, id string) error

DeleteUser deletes a user and all associated data (accounts and sessions).

This performs a cascading delete to maintain referential integrity:

  1. Delete all sessions (logs user out from all devices)
  2. Delete all accounts (credentials, OAuth connections, etc.)
  3. Delete the user record itself

The deletion order ensures that foreign key constraints are satisfied. If any step fails, subsequent steps are still attempted (best-effort cleanup).

Parameters:

  • ctx: Request context
  • id: User ID to delete

Returns an error only if the user deletion itself fails. Session and account deletion errors are logged but don't fail the operation.

func (*UserService) GetUserByEmail

func (s *UserService) GetUserByEmail(ctx context.Context, email string) (auth.User, error)

GetUserByEmail retrieves a user by their email address.

func (*UserService) GetUserByID

func (s *UserService) GetUserByID(ctx context.Context, id string) (auth.User, error)

GetUserByID retrieves a user by their unique ID.

func (*UserService) UpdateUser

func (s *UserService) UpdateUser(ctx context.Context, user auth.User) error

UpdateUser updates an existing user's information.

func (*UserService) UpdateUserEmail

func (s *UserService) UpdateUserEmail(ctx context.Context, userID, email string) error

UpdateUserEmail updates a user's email

type ValidationError

type ValidationError struct {
	// Field is the name of the field that failed validation
	Field string

	// Message is a human-readable description of what's wrong
	Message string
}

ValidationError represents a validation error for a specific field. Used when input validation fails for user-provided data.

func (ValidationError) Error

func (e ValidationError) Error() string

type ValidationErrors

type ValidationErrors []ValidationError

ValidationErrors represents multiple validation errors. Useful when validating entire request bodies and returning all errors at once rather than failing on the first issue.

func GetValidationErrors

func GetValidationErrors(err error) ValidationErrors

GetValidationErrors extracts all validation errors from an error

func (ValidationErrors) Error

func (e ValidationErrors) Error() string

func (ValidationErrors) Errors

func (e ValidationErrors) Errors() []ValidationError

Errors returns all validation errors as a slice for iteration.

type VerificationModel

type VerificationModel interface {
	// GetID returns the unique identifier for this verification
	GetID() string

	// SetID assigns a unique identifier to this verification
	SetID(string)

	// GetToken returns the verification token/code
	GetToken() string

	// SetToken assigns the verification token/code
	SetToken(string)

	// GetIdentifier returns the target of verification (e.g., email address)
	GetIdentifier() string

	// SetIdentifier assigns the target identifier
	SetIdentifier(string)

	// SetCreatedAt assigns the creation timestamp
	SetCreatedAt(time.Time)

	// GetExpiresAt returns when this verification expires
	GetExpiresAt() time.Time

	// SetExpiresAt assigns the verification expiration time
	SetExpiresAt(time.Time)
}

VerificationModel defines the required methods for a verification model implementation. Verifications are temporary tokens used for email confirmation, password resets, etc.

type VerificationService

type VerificationService struct {
	// contains filtered or unexported fields
}

VerificationService manages temporary verification tokens for various flows:

  • Email verification after signup
  • Password reset tokens
  • OTP (one-time password) codes
  • Magic link tokens
  • Custom verification workflows

All verifications have:

  • A unique token/code
  • An identifier (email, phone, etc.)
  • A type ("email", "reset", "otp", etc.)
  • An expiration time

Verifications are single-use and should be deleted or invalidated after use.

func NewVerificationService

func NewVerificationService(store auth.VerificationStore, auditLogger AuditLogger) *VerificationService

NewVerificationService creates a new verification service.

func (*VerificationService) CreateVerification

func (s *VerificationService) CreateVerification(ctx context.Context, identifier, vType string, expiry time.Duration, customToken *string) (auth.Verification, error)

CreateVerification creates a new verification token for a specific purpose.

Generates a cryptographically secure random token (or uses a custom token if provided). The verification is stored with an expiration time for automatic cleanup.

Common use cases:

  • Email verification: CreateVerification(ctx, email, "email", 24*time.Hour, nil)
  • Password reset: CreateVerification(ctx, email, "reset", 1*time.Hour, nil)
  • Custom OTP: CreateVerification(ctx, phone, "otp", 10*time.Minute, &customCode)

Parameters:

  • ctx: Request context
  • identifier: Target being verified (email, phone, user ID, etc.)
  • vType: Verification type ("email", "reset", "otp", etc.)
  • expiry: How long the token is valid
  • customToken: Optional custom token (if nil, random hex token is generated)

Returns the created verification with populated token.

func (*VerificationService) DeleteVerification

func (s *VerificationService) DeleteVerification(ctx context.Context, id string) error

DeleteVerification permanently deletes a verification token.

Use this for cleanup after successful verification or when canceling a verification flow.

Parameters:

  • ctx: Request context
  • id: Verification ID to delete

func (*VerificationService) InvalidateVerification

func (s *VerificationService) InvalidateVerification(ctx context.Context, identifier, vType string) error

InvalidateVerification marks all tokens of a specific type for an identifier as invalid.

This prevents token reuse after successful verification. For example, after a user verifies their email, all pending email verification tokens for that address should be invalidated.

Parameters:

  • ctx: Request context
  • identifier: The target identifier (email, phone, etc.)
  • vType: The verification type to invalidate ("email", "reset", etc.)

func (*VerificationService) ValidateVerification

func (s *VerificationService) ValidateVerification(ctx context.Context, token string) (auth.Verification, error)

ValidateVerification validates a token and returns the verification if valid.

Checks:

  1. Token exists in storage
  2. Token has not expired

After successful validation, the caller should typically:

  • Perform the verified action (activate account, reset password, etc.)
  • Invalidate the token to prevent reuse

Parameters:

  • ctx: Request context
  • token: The token string to validate

Returns:

  • The verification record if valid
  • AuthErrorCodeTokenExpired if expired
  • Error if token not found

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL