v3.0.0
What's Changed
BREAKING CHANGES:
- Minimum PHP version raised to 8.1 (was 8.0)
- Dropped Laravel 8 and 9 support (both EOL)
- withExpiration() now validates timestamp (must be positive, future, after activation)
- withActivation() now validates timestamp (must be positive, before expiration)
- UrlService::attempt() now throws RateLimitExceededException after too many attempts
- Password validation now properly enforced (6-32 characters)
New Features:
- Multi-domain support with per-domain configuration (opt-in)
- Per-domain prefix and identifier length
- Same identifier can exist on different domains
- DomainResolver service with host/subdomain/path strategies
- UrlBuilder: forDomain(), onDomain(), forCurrentDomain(), withPrefix(), withIdentifierLength()
- UrlService: findByDomain(), domain parameter on all lookup methods
- URL validation to prevent open redirect and SSRF attacks (opt-in)
- Rate limiting for password-protected URL attempts
- UrlService::findOrCreate() for idempotent URL creation
- Laravel 11 and 12 compatibility
- PHP 8.1 through 8.5 support
Bug Fixes:
- Fix str_ends_with/str_replace argument order in ShortUrlHelper
- Replace getenv() with Laravel's env() helper
- Fix password validation operator precedence
- Improve password comparison security (timing-safe)
- Fix 404 handling for invalid identifiers (was returning 500)
- Fix exception chain preservation throughout codebase
Security:
- URL validation blocks javascript:, data:, file: protocols
- URL validation blocks private IPs and cloud metadata endpoints
- Rate limiting prevents password brute-force attacks
- Timing-safe password comparison prevents timing attacks
Full Changelog: v2.1.2...v3.0.0