import { Context } from 'hono'; import { BlockInfo } from './index.js'; /** * hono-honeypot/abuseipdb — optional AbuseIPDB community reporter. * * Reports IPs that the honeypot bans back to the shared AbuseIPDB abuse * database. Wire it into `honeypot({ onBlocked })`. It is intentionally a * separate entry point so the core middleware stays zero-dependency and * vendor-neutral — import it only if you want AbuseIPDB. * * Design notes: * - **Injectable `fetch`** (the "fetcher"): defaults to `globalThis.fetch`, but * you can pass a bound/service-binding fetch for Workers or a fake for tests. * - **Key resolution is edge-portable**: an explicit `apiKey` wins; otherwise it * reads `c.env[envKey]` (Cloudflare Workers / Bun / Deno bindings) and falls * back to `process.env[envKey]` (Node). `process.env` is empty by default on * Workers, which is why context resolution matters. * - **Reports once, on ban**: by default only fires when `info.banned === true`, * keeping you well under AbuseIPDB's 1000/day + 15-min-per-IP limits. * - **Never throws, never blocks**: fire-and-forget; all failures are logged and * swallowed so the request path is unaffected. * * @example * ```ts * import { honeypot, MemoryStore } from 'hono-honeypot'; * import { abuseIPDBReporter } from 'hono-honeypot/abuseipdb'; * * app.use('*', honeypot({ * store: new MemoryStore(), * onBlocked: abuseIPDBReporter(), // reads ABUSEIPDB_API_KEY from c.env / process.env * })); * ``` * * @see https://docs.abuseipdb.com/ * @license MIT */ /** * Minimal fetch-like signature for the injected fetcher. Deliberately structural * rather than `typeof fetch`, so any fetch-like function works — a Workers service * binding, a wrapped/instrumented fetch, or a test fake — without having to carry * the global's extra static members (e.g. `preconnect`). */ type Fetcher = (input: string, init?: RequestInit) => Promise; interface AbuseIPDBOptions { /** * AbuseIPDB API key, or a resolver from the request context. * When omitted, the key is resolved from the environment (see {@link envKey}). */ apiKey?: string | ((c: Context) => string | undefined); /** * Environment variable name to resolve the key from when {@link apiKey} is not given. * Checked on `c.env` first (Workers/Bun/Deno bindings), then `process.env` (Node). * @default 'ABUSEIPDB_API_KEY' */ envKey?: string; /** * Injected fetch implementation (the "fetcher"). Use to pass a bound fetch on * Workers, or a fake in tests. Any fetch-like function is accepted. * @default globalThis.fetch */ fetch?: Fetcher; /** * AbuseIPDB report category ids, comma-separated. * @default '21,19' (Web App Attack, Bad Web Bot) */ categories?: string; /** * Build the public report comment from the block info. The result is sanitized * to printable ASCII and capped before sending. Keep it to the attacker's own * request line — the comment is published publicly on AbuseIPDB. * @default `Honeypot: automated vulnerability scan / web app attack. Last probe: ${method} ${path}` */ comment?: (info: BlockInfo) => string; /** * Decide whether a given block should be reported. Report sparingly to respect * AbuseIPDB rate limits. * @default (info) => info.banned === true */ reportOn?: (info: BlockInfo) => boolean; /** * Override the report endpoint (mainly for testing). * @default 'https://api.abuseipdb.com/api/v2/report' */ endpoint?: string; } /** * Create an `onBlocked`-compatible reporter that submits banned IPs to AbuseIPDB. * * The returned function is fire-and-forget: it never throws and never blocks the * request. Without a resolvable API key (or for `ip === 'unknown'`) it is a no-op, * so it is safe to wire up unconditionally. */ declare function abuseIPDBReporter(options?: AbuseIPDBOptions): (info: BlockInfo, c: Context) => Promise; export { type AbuseIPDBOptions, type Fetcher, abuseIPDBReporter };