# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.9] - 2026-01-26
### Added
- `ensure!` guard macro for concise precondition checks.
- `module!` macro for route groups plus OpenAPI module helpers.
- Alba-style testing helpers (`TestApp`, JSON helpers, `post_json`, status/header assertions).
- Auth extractor caching for users and claims (`ClaimsRef`) to avoid duplicate verification.
- Expanded docs and README examples for auth extractors, testing helpers, OpenAPI modules, and CLI flags.
## [0.7.8] - 2026-01-23
### Changed
- Metrics and request logging now prefer route templates when available, reducing label cardinality.
- Request logging skips formatting work when the configured log level is disabled.
- Feature matrix added to README; install snippet updated.
- Added optional `feature-gate-warnings` and `feature-gate-errors` for clearer feature diagnostics.
- Added tests covering MatchedPath metrics and logging guard behavior.
## [0.7.7] - 2026-01-23
### Changed
- Seat add-ons via Checkout are now rejected; use `SeatManager::add_seats` (proration-safe).
- `create_seat_checkout_session` is deprecated in favor of `SeatManager::add_seats`.
## [0.7.6] - 2026-01-23
### Security
- Password reset and email verification tokens now use OS CSPRNG for generation.
- MFA tokens are hashed before storage and consumption in the login flow.
- Trusted-device verification now rejects missing fingerprints when fingerprint validation is enabled.
## [0.7.1] - 2025-12-12
### Fixed
- **Compilation fix**: Updated `tower-http` minimum version to 0.6.7 to ensure `TimeoutLayer::with_status_code()` method is available. Version 0.7.0 would fail to compile with older tower-http versions.
## [0.7.0] - 2025-12-12
### Security
This release contains important security hardening for enterprise deployments.
- **ConsoleMailer now redacts email content by default.** Email body content is no longer printed to stdout to prevent sensitive data (tokens, PII, verification links) from being captured in container logs.
- **Webhook signature prefix is now strictly enforced.** When using `HmacSha256Verifier::new_with_prefix()`, signatures missing the required prefix are now rejected instead of silently accepting unprefixed signatures.
- **Database credentials protected in all output.** Both `Debug` and `Serialize` implementations for `DatabaseConfig` now redact the password, preventing credential leakage in logs, JSON serialization, and error messages.
- **JWT warning spam eliminated.** Security warnings for missing issuer/audience validation now fire only once per verifier instance using `OnceLock`.
### Added
- `RedisJobQueue::shutdown()` - Gracefully stop the background scheduler task
- `RedisJobQueue::ping()` - Async health check that updates cached status
- `InMemoryJobQueue::shutdown()` - Gracefully stop the background scheduler task
- `InMemoryJobQueue::with_history_limit()` - Create queue with custom completed/failed history size
- `RedisCache::ping()` - Async health check that updates cached status
- `SeaOrmPool::ping()` - Async health check that updates cached status
- `ConnectionManager::reconcile_counter()` - Detect and correct connection counter drift
- `ConsoleMailer::with_full_output()` - Opt-in to see full email content (development only)
- Configuration validation warnings for invalid environment variables (compression, timeout, session configs)
### Changed
- **BREAKING**: `ConsoleMailer` now redacts email body content by default. Use `.with_full_output(true)` to see full content.
- **BREAKING**: `HmacSha256Verifier::new_with_prefix()` now rejects signatures missing the required prefix (was silently accepting).
- `InMemoryJobQueue` completed/failed lists are now bounded (default 10,000) to prevent unbounded memory growth.
- `is_healthy()` methods on `RedisCache`, `RedisJobQueue`, `InMemoryJobQueue`, and `SeaOrmPool` now return cached status from `ping()` instead of blocking.
- Database pool configuration now validates limits: `max_connections` (1-1000), `connect_timeout` (1-300 seconds).
- Signal handlers in `App::serve()` use fallback instead of panicking on failure.
- Shutdown grace period increased from 2 to 5 seconds.
### Fixed
- **Blocking I/O in async context**: `InMemoryJobQueue::is_healthy()` used `blocking_lock()` which could deadlock the async runtime. Now uses `AtomicBool`.
- **Resource leak**: Background scheduler tasks in `RedisJobQueue` and `InMemoryJobQueue` now have proper shutdown mechanisms.
- **Unbounded memory growth**: `InMemoryJobQueue` completed/failed job lists now have configurable size limits.
- **Panic in production**: Removed `expect()` calls in `App::serve()` that could panic on invalid config or signal handler failures.
- **WebSocket connection counter drift**: Added `reconcile_counter()` to detect and correct atomic counter drift.
### Migration Guide
#### ConsoleMailer (Breaking Change)
If you use `ConsoleMailer` for development and need to see full email content:
```rust
// Before (0.6.x) - full content shown by default
let mailer = ConsoleMailer::new();
// After (0.7.0) - content redacted by default
let mailer = ConsoleMailer::new(); // Shows: "[TEXT] 42 bytes [REDACTED]"
// To see full content (development only):
let mailer = ConsoleMailer::new().with_full_output(true);
```
#### Webhook Signature Verification (Breaking Change)
If you use `HmacSha256Verifier::new_with_prefix()`, signatures **must** now include the prefix:
```rust
// Verifier configured with prefix
let verifier = HmacSha256Verifier::new_with_prefix(secret, "sha256=");
// Before (0.6.x) - both would pass:
verifier.verify_signature(payload, "sha256=abc123...").await // OK
verifier.verify_signature(payload, "abc123...").await // Also OK (wrong!)
// After (0.7.0) - prefix is required:
verifier.verify_signature(payload, "sha256=abc123...").await // OK
verifier.verify_signature(payload, "abc123...").await // FAILS (correct!)
```
#### Job Queue Shutdown (Recommended)
For clean resource cleanup, call `shutdown()` before dropping job queues:
```rust
// Redis job queue
let queue = RedisJobQueue::new("redis://localhost", None, 3, 5)?;
// ... use queue ...
queue.shutdown().await; // NEW: Clean shutdown
// In-memory job queue
let queue = InMemoryJobQueue::new(3, 60);
// ... use queue ...
queue.shutdown().await; // NEW: Clean shutdown
```
#### Health Check Pattern (Recommended)
For accurate health status, run periodic `ping()` calls:
```rust
// Database pool
let pool = SeaOrmPool::from_config(&config).await?;
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
pool.ping().await; // Updates cached health status
}
});
// is_healthy() now returns cached status (non-blocking)
if pool.is_healthy() { /* ... */ }
```
## [0.6.0] - 2025-12-12
### Security
**Cookie sessions are now properly encrypted.** Previously, session data was stored as plaintext JSON despite having an encryption key configured. This version implements authenticated encryption using XChaCha20-Poly1305 via the `cookie` crate's private cookies.
### Changed
- **BREAKING**: Cookie session encryption key must now be 64 bytes (128 hex characters), not 32 bytes. Generate with: `openssl rand -hex 64`
- Session cookies are now encrypted and authenticated - tampered cookies are rejected
- Added `encrypt()` and `decrypt()` public methods to `CookieSessionStore` for direct use
- Updated session documentation with correct key size requirements
### Migration Guide
If you were using cookie sessions, regenerate your encryption key:
```bash
# Old (no longer works):
# openssl rand -hex 32
# New (required):
openssl rand -hex 64
export SESSION_ENCRYPTION_KEY=your-128-char-hex-key
```
**Important:** Existing sessions will be invalidated when you upgrade, as the encryption format has changed.
## [0.5.0] - 2025-12-12
### Security
This release contains important security fixes. Users are strongly encouraged to upgrade.
- **Webhook HMAC verification**: Implemented proper HMAC-SHA256 signature verification with timing-safe comparison using the `subtle` crate. Previously, the webhook verifier accepted any non-empty signature.
- **Session encryption key requirement**: Cookie sessions now require an explicit encryption key. Without it, `CookieSessionStore::new()` returns an error. Use `allow_insecure_key: true` for development only.
- **Error information disclosure (CWE-209)**: Server errors (5xx) now hide internal details in production, preventing leakage of database credentials, internal hostnames, SQL queries, and stack traces.
- **Rate limiter IP spoofing**: Added `trust_proxy` configuration (default: `false`). X-Forwarded-For headers are now ignored by default to prevent IP spoofing attacks.
- **CORS disabled by default**: CORS is now disabled by default and must be explicitly enabled.
### Added
- `HmacSha256Verifier` for webhook signature verification with support for hex, base64, and prefixed signatures
- `trust_proxy` configuration for rate limiting
- `allow_insecure_key` configuration for session development mode
- `safe_message()` method on `TidewayError` for production-safe error messages
- Comprehensive test suite (200+ tests)
- Webhook documentation (`docs/webhooks.md`)
### Changed
- **BREAKING**: CORS `enabled` now defaults to `false` (was `true`)
- **BREAKING**: Rate limiter `trust_proxy` defaults to `false` - X-Forwarded-For is no longer trusted by default
- **BREAKING**: Cookie sessions require `encryption_key` or `allow_insecure_key: true`
- Session encryption key error now returns `Result` instead of panicking
- Updated `sessions.md`, `rate_limiting.md`, and `error_handling.md` documentation
### Dependencies
- Added `subtle` crate for timing-safe cryptographic operations
- `hmac` and `sha2` crates (already present) now used for webhook verification
### Migration Guide
#### CORS Configuration
CORS is now disabled by default. To enable:
```rust
// Option 1: Use permissive() for development
let cors = CorsConfig::permissive();
// Option 2: Explicitly enable with origins
let cors = CorsConfig::builder()
.enabled(true)
.allow_origin("https://example.com")
.build();
```
#### Session Configuration
Cookie sessions now require a 64-byte encryption key (128 hex characters):
```bash
# Generate a key
openssl rand -hex 64
# Set environment variable
export SESSION_ENCRYPTION_KEY=your-128-char-hex-key
```
Or for development only:
```rust
let config = SessionConfig {
allow_insecure_key: true, // WARNING: Never use in production!
..Default::default()
};
```
#### Rate Limiting Behind a Proxy
If your application is behind nginx, AWS ALB, or Cloudflare:
```rust
let rate_limit = RateLimitConfig::builder()
.enabled(true)
.per_ip()
.trust_proxy(true) // Enable this if behind a trusted proxy
.build();
```
Or via environment:
```bash
export RATE_LIMIT_TRUST_PROXY=true
```
## [0.4.0] - 2025-12-11
### Added
- Comprehensive test coverage for core modules
### Changed
- **BREAKING**: `SeaOrmPool::as_ref()` renamed to `SeaOrmPool::inner()` to avoid confusion with `std::convert::AsRef` trait
- **BREAKING**: `DatabaseConnection::as_ref()` renamed to `DatabaseConnection::inner()`
- **BREAKING**: `TestUser::default()` renamed to `TestUser::generate()` to avoid confusion with `std::default::Default` trait
### Fixed
- Fixed deprecated `TimeoutLayer::new` usage (now uses `with_status_code`)
- Fixed all clippy warnings across the codebase
- Improved code quality with idiomatic Rust patterns
### Migration Guide
#### Database Connection Changes
If you were using `as_ref()` to get the inner SeaORM connection:
```rust
// Before (0.3.x)
let conn = sea_orm_pool.as_ref();
// After (0.4.0)
let conn = sea_orm_pool.inner();
```
#### TestUser Changes
If you were using `TestUser::default()` in tests:
```rust
// Before (0.3.x)
let user = TestUser::default();
// After (0.4.0)
let user = TestUser::generate();
```
The rename to `generate()` better reflects that this creates a new user with randomly generated fake data, not a "default" user with empty/zero values.
## [0.3.0] - 2025-12-11
### Added
- **Email support** with `Mailer` trait for sending transactional emails
- `ConsoleMailer` for development (prints emails to stdout)
- `SmtpMailer` using lettre for production SMTP
- `SmtpConfig` with builder pattern and `from_env()` for configuration
- `AppContext::with_mailer()` builder method for dependency injection
- Documentation for third-party email services (Resend, SendGrid, Postmark, AWS SES)
- Email example and comprehensive test suite
## [0.2.1] - 2025-12-11
### Fixed
- Various bug fixes and improvements
## [0.2.0] - 2025-12-11
### Added
- Background jobs system with `JobQueue` trait
- WebSocket support with connection management
- In-memory and Redis-backed job queues
## [0.1.0] - Initial Release
### Added
- Core framework with Axum integration
- Route modules and modular architecture
- Database support with SeaORM
- Cache and session traits
- Request validation
- Health checks
- Prometheus metrics
- Compression and security middleware
- Alba-style testing utilities