Files
SkinbaseNova/docs/registration-antispam.md

5.3 KiB

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.