Abuse Protection And Risk Adapters
Compose bot, password, email, GeoIP, and IP reputation policies without coupling auth flows to providers.
All provider boundaries are public Effect services and can be replaced with an application Layer. Default/noop policies compile without Turnstile, HIBP, or IPQualityScore credentials.
Bot ordering and coverage
Auth HTTP handlers run validation/origin checks and AuthRateLimit rate limits before BotProtection. Bot verification completes before auth, risk/domain work, challenge creation, or email delivery. Combined email start verifies once with email-auth, then creates both OTP and magic-link challenges.
| Endpoint | BotProtectionOperation |
|---|---|
| Password signup / signin | sign-up, sign-in |
| Password reset start / verify | password-reset-start, password-reset-verify |
| Email OTP start / verify | email-otp-start, email-otp-verify |
| Magic link start / verify | magic-link-start, magic-link-verify |
| Combined email start | email-auth |
| Email verification start | email-verification-start |
| Identity availability | identity-availability |
The ready-made HTTP preset skips bot verification and requires no provider credentials. App-owned endpoints bind behavior directly with PasswordGuards, EmailGuards, or IdentityGuards and .withPolicy({ botProtection: BotProtection.verify(...) }), while provider infrastructure such as CloudflareTurnstileLive remains global. Every named guard applies rate limiting before bot verification, strips browser-only proof fields, and returns a dedicated domain-ready result. Authenticated identity guards validate the session first and derive their rate-limit subject from that session. Lower-level compositions can still combine BotProtectionDefaultLive, an application BotProtectionPolicy, and a verifier. Match the widget action exactly, allowlist deployed hostnames, set an explicit fail-open or fail-closed outage mode, and use a bounded timeout. Fail-open applies only to explicitly retryable provider outages; invalid local configuration always fails safe as an internal error. Browser tokens and provider details remain redacted and public failures are generic.
Password and email risk
HibpPwnedPasswordsLive hashes locally and sends only a five-character SHA-1 prefix with response padding. Its optional injected cache stores bounded normalized prefix ranges for 6-24 hours and can serve stale entries only on retryable outages. PasswordRiskPolicy sets thresholds and failure policy at signup, reset completion, initial set, and change. Validation runs first; change verifies the current password before checking the replacement. Never persist a password, full hash/suffix, or HIBP payload.
DisposableEmailDomainsLive takes an app-pinned dataset and performs no request-time download. Record a reviewed CC0 source revision and digest beside the snapshot. The app allowlist wins over the pinned data and app denylist. Apply EmailAcceptancePolicy to new email signup and optional identity add/replace; existing login is never blocked.
Geo and IP reputation
CloudflareRequestMetadataLive reads Worker request.cf and omits CF-Connecting-IP by default. Enable trustCloudflareConnectingIp only at a verified Cloudflare boundary. Its durable projection excludes raw IP and city and keeps coarse geo facts. It does not use MaxMind.
IpQualityScoreLive maps a bounded response to proxy, VPN, Tor, datacenter, and risk-level facts. Application policy decides what those facts mean and how outages behave. Keep API keys redacted, enforce timeouts, use HMAC-derived cache/device keys, and never durably retain raw IP, token, password, provider payload, request ID, or diagnostics.
The concise provider-free and opt-in compositions are type checked in packages/core/test-d/MilestoneBComposition.type-test.ts. The legacy recipe is docs/recipes/features/abuse-protection-and-risk-adapters.md.