# 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 (Turnstile)

Service:

- `app/Services/Security/TurnstileVerifier.php`

Controller logic (`RegisteredUserController::shouldRequireTurnstile`):

- Requires Turnstile for suspicious IP activity (attempt threshold)
- Also requires Turnstile when registration rate-limit state is detected

UI behavior (`resources/views/auth/register.blade.php`):

- Turnstile widget is only rendered when required

### 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`

Turnstile config:

- `config/services.php` under `turnstile`

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
- Turnstile 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 `TURNSTILE_SITE_KEY` and `TURNSTILE_SECRET_KEY` in production.
