harden(nip05): rate-limit the claim/release endpoints

A blunt per-IP in-memory throttle (12/min), same pattern as the AI proxy,
so a script can't mass-squat names. The real gate remains the signature on
every claim.
This commit is contained in:
SkateSpots 2026-09-28 12:19:15 +07:00
parent 3c7c8bd87e
commit 87f7a1f86a

View file

@ -112,6 +112,26 @@ function verifyAuth(header, action) {
return { pubkey: ev.pubkey, name: normalizeName(tagVal(ev.tags, 'name')) };
}
// Best-effort in-memory throttle for the write endpoints. Serverless instances
// are ephemeral, so this is a blunt abuse damper (mass name-squatting), not a
// hard guarantee — the real gate is the signature on every claim.
const WINDOW_MS = 60_000;
const LIMIT = 12;
const hits = new Map();
function throttled(request) {
const ip =
request.headers.get('x-nf-client-connection-ip') ||
request.headers.get('x-forwarded-for') ||
'unknown';
const now = Date.now();
const recent = (hits.get(ip) ?? []).filter((t) => now - t < WINDOW_MS);
if (recent.length >= LIMIT) return true;
recent.push(now);
hits.set(ip, recent);
return false;
}
// ── Handlers ──────────────────────────────────────────────────────────────────
/** GET /.well-known/nostr.json[?name=x] — the NIP-05 document itself. */
@ -153,6 +173,7 @@ async function handleCheck(url) {
/** POST /api/nip05/claim — reserve `name` for the signing pubkey. */
async function handleClaim(request) {
if (throttled(request)) return json({ error: 'Too many requests.' }, 429);
const auth = verifyAuth(request.headers.get('x-nip05-auth'), 'claim');
if (!auth) return json({ error: 'Signature required.' }, 401);
const { name, pubkey } = auth;
@ -180,6 +201,7 @@ async function handleClaim(request) {
/** POST /api/nip05/release — remove a name (its owner, or a platform owner). */
async function handleRelease(request) {
if (throttled(request)) return json({ error: 'Too many requests.' }, 429);
const auth = verifyAuth(request.headers.get('x-nip05-auth'), 'release');
if (!auth) return json({ error: 'Signature required.' }, 401);
const { name, pubkey } = auth;