210 lines
5.8 KiB
Markdown
210 lines
5.8 KiB
Markdown
# Registration Anti-Spam + Email Quota Protection
|
|
|
|
This document describes how the Skinbase email-first registration hardening works.
|
|
|
|
## Scope
|
|
|
|
Applies to the flow:
|
|
|
|
- `GET /register`
|
|
- `POST /register`
|
|
- `GET /register/notice`
|
|
- `POST /register/resend-verification`
|
|
- `GET /verify/{token}`
|
|
- `GET/POST /setup/password`
|
|
- `GET/POST /setup/username`
|
|
|
|
Primary implementation:
|
|
|
|
- `app/Http/Controllers/Auth/RegisteredUserController.php`
|
|
- `app/Http/Controllers/Auth/RegistrationVerificationController.php`
|
|
|
|
## Security Controls
|
|
|
|
### 1) IP Rate Limiting
|
|
|
|
Defined in `app/Providers/AppServiceProvider.php`:
|
|
|
|
- `register-ip`: per-minute IP limit
|
|
- `register-ip-daily`: per-day IP limit
|
|
- `register` (legacy resend route): per-minute IP + per-email key
|
|
|
|
Applied on `POST /register` in `routes/auth.php`:
|
|
|
|
- `throttle:register-ip`
|
|
- `throttle:register-ip-daily`
|
|
|
|
### 2) Per-Email Cooldown
|
|
|
|
Cooldown is enforced by user fields:
|
|
|
|
- `users.last_verification_sent_at`
|
|
- `users.verification_send_count_24h`
|
|
- `users.verification_send_window_started_at`
|
|
|
|
On repeated requests within cooldown:
|
|
|
|
- No additional verification email is queued
|
|
- Generic success message is returned
|
|
|
|
### 3) Progressive CAPTCHA
|
|
|
|
Service:
|
|
|
|
- `app/Services/Security/CaptchaVerifier.php`
|
|
- `app/Services/Security/TurnstileVerifier.php` (legacy compatibility wrapper)
|
|
|
|
Controller logic (`RegisteredUserController::shouldRequireCaptcha`):
|
|
|
|
- Requires CAPTCHA for suspicious IP activity (attempt threshold)
|
|
- Also requires CAPTCHA when registration rate-limit state is detected
|
|
- Active provider is selected through `forum_bot_protection.captcha.provider`
|
|
|
|
UI behavior (`resources/views/auth/register.blade.php`):
|
|
|
|
- Provider-specific widget is only rendered when required
|
|
- Turnstile, reCAPTCHA, and hCaptcha are supported
|
|
|
|
### 4) Disposable Domain Block
|
|
|
|
Service:
|
|
|
|
- `app/Services/Auth/DisposableEmailService.php`
|
|
|
|
Config source:
|
|
|
|
- `config/disposable_email_domains.php`
|
|
|
|
Behavior:
|
|
|
|
- Blocks known disposable domains (supports wildcard matching)
|
|
- Returns friendly validation error
|
|
|
|
### 5) Queue + Throttle + Quota Circuit Breaker
|
|
|
|
Queue job:
|
|
|
|
- `app/Jobs/SendVerificationEmailJob.php`
|
|
|
|
Behavior:
|
|
|
|
- Registration controller dispatches `SendVerificationEmailJob`
|
|
- Job applies global send throttling via `RateLimiter`
|
|
- Job checks monthly quota via `RegistrationEmailQuotaService`
|
|
- If quota exceeded: send is blocked (fail closed), event marked blocked
|
|
|
|
Quota service/model/table:
|
|
|
|
- `app/Services/Auth/RegistrationEmailQuotaService.php`
|
|
- `app/Models/SystemEmailQuota.php`
|
|
- `system_email_quota`
|
|
|
|
Send event audit:
|
|
|
|
- `app/Models/EmailSendEvent.php`
|
|
- `email_send_events`
|
|
|
|
### 6) Generic Responses (Anti-Enumeration)
|
|
|
|
The registration entry point uses a standard success message:
|
|
|
|
- `If that email is valid, we sent a verification link.`
|
|
|
|
This message is returned for:
|
|
|
|
- Unknown emails
|
|
- Existing verified emails
|
|
- Cooldown cases
|
|
- Quota-blocked paths
|
|
|
|
### 7) Verification Token Hardening
|
|
|
|
Service:
|
|
|
|
- `app/Services/Auth/RegistrationVerificationTokenService.php`
|
|
|
|
Protections:
|
|
|
|
- Token generated with high entropy (`Str::random(64)`)
|
|
- Stored hashed (`sha256`) in `user_verification_tokens`
|
|
- Expires using configured TTL
|
|
- Validation uses hash lookup + constant-time compare (`hash_equals`)
|
|
- Token deleted after successful verification (one-time use)
|
|
|
|
Verification endpoint:
|
|
|
|
- `app/Http/Controllers/Auth/RegistrationVerificationController.php`
|
|
|
|
## Configuration
|
|
|
|
Main registration config:
|
|
|
|
- `config/registration.php`
|
|
|
|
Key settings:
|
|
|
|
- `ip_per_minute_limit`
|
|
- `ip_per_day_limit`
|
|
- `email_per_minute_limit`
|
|
- `email_cooldown_minutes`
|
|
- `verify_token_ttl_hours`
|
|
- `enable_turnstile`
|
|
- `disposable_domains_enabled`
|
|
- `turnstile_suspicious_attempts`
|
|
- `turnstile_attempt_window_minutes`
|
|
- `email_global_send_per_minute`
|
|
- `monthly_email_limit`
|
|
- `generic_success_message`
|
|
|
|
Captcha provider config:
|
|
|
|
- `config/services.php` under `turnstile`, `recaptcha`, and `hcaptcha`
|
|
- `config/forum_bot_protection.php` under `captcha`
|
|
|
|
Environment examples:
|
|
|
|
- `.env.example` contains all registration anti-spam keys
|
|
|
|
## Database Objects
|
|
|
|
Added for anti-spam/quota support:
|
|
|
|
- Migration: `2026_02_21_000001_add_registration_antispam_fields_to_users_table.php`
|
|
- Migration: `2026_02_21_000002_create_email_send_events_table.php`
|
|
- Migration: `2026_02_21_000003_create_system_email_quota_table.php`
|
|
- Migration: `2026_02_20_191000_add_registration_phase1_schema.php` (creates `user_verification_tokens`)
|
|
- Migration: `2026_02_21_000004_rename_token_to_token_hash_in_user_verification_tokens.php` (schema hardening)
|
|
- Migration: `2026_02_21_000005_ensure_user_verification_tokens_table_exists.php` (rollout safety)
|
|
|
|
## Test Coverage
|
|
|
|
Primary tests:
|
|
|
|
- `tests/Feature/Auth/RegistrationAntiSpamTest.php`
|
|
- `tests/Feature/Auth/RegistrationNoticeResendTest.php`
|
|
- `tests/Feature/Auth/RegistrationQuotaCircuitBreakerTest.php`
|
|
- `tests/Feature/Auth/RegistrationTokenVerificationTest.php`
|
|
- `tests/Feature/Auth/RegistrationFlowChecklistTest.php`
|
|
- `tests/Feature/Auth/RegistrationVerificationMailTest.php`
|
|
|
|
Covered scenarios:
|
|
|
|
- IP rate-limit returns `429`
|
|
- Cooldown suppresses extra sends
|
|
- Disposable domains blocked
|
|
- Quota exceeded blocks send and keeps generic success UX
|
|
- CAPTCHA required on abuse/rate-limit state
|
|
- Tokens hashed, expire, and are one-time
|
|
- Responses avoid account enumeration
|
|
|
|
## Operations Notes
|
|
|
|
- Keep disposable domain list maintained in `config/disposable_email_domains.php`.
|
|
- Ensure queue workers process the `mail` queue.
|
|
- Monitor `email_send_events` for blocked/sent patterns.
|
|
- Set `REGISTRATION_MONTHLY_EMAIL_LIMIT` based on provider quota.
|
|
- Configure the active CAPTCHA provider keys in production:
|
|
- Turnstile: `TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY`
|
|
- reCAPTCHA: `RECAPTCHA_ENABLED`, `RECAPTCHA_SITE_KEY`, `RECAPTCHA_SECRET_KEY`
|
|
- hCaptcha: `HCAPTCHA_ENABLED`, `HCAPTCHA_SITE_KEY`, `HCAPTCHA_SECRET_KEY`
|