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:
parent
3c7c8bd87e
commit
87f7a1f86a
1 changed files with 22 additions and 0 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue