feat: add Retry-After header and countdown timer for rate limits
- Rate limiter returns retryAfterSec in response body + Retry-After header - Registration rate limits: 10 per 10min (was 15/hour) - Frontend surfaces retry timer in error messages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
592841d7b1
commit
3c32f01aa7
@@ -260,8 +260,9 @@ export function useNostr() {
|
|||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// Surface detailed error info from webhook verification failures
|
// Include retry timer info for rate limits
|
||||||
const msg = data.details ? `${data.error} ${data.details}` : (data.error || 'Registration failed')
|
let msg = data.details ? `${data.error} ${data.details}` : (data.error || 'Registration failed')
|
||||||
|
if (res.status === 429 && data.retryAfterSec) msg += ` (${data.retryAfterSec}s)`
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +345,11 @@ export function useNostr() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!res.ok) throw new Error(data.error || 'Registration failed')
|
if (!res.ok) {
|
||||||
|
let msg = data.error || 'Registration failed'
|
||||||
|
if (res.status === 429 && data.retryAfterSec) msg += ` (${data.retryAfterSec}s)`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
bot.value = {
|
bot.value = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ export function rateLimit(windowMs: number, maxHits: number) {
|
|||||||
} else {
|
} else {
|
||||||
entry.count++
|
entry.count++
|
||||||
if (entry.count > maxHits) {
|
if (entry.count > maxHits) {
|
||||||
return c.json({ error: 'Too many requests. Slow down.' }, 429)
|
const retryAfterSec = Math.ceil((entry.resetAt - now) / 1000)
|
||||||
|
c.header('Retry-After', String(retryAfterSec))
|
||||||
|
return c.json({ error: 'Too many requests. Slow down.', retryAfterSec }, 429)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ authRouter.post('/login', rateLimit(60_000, 30), async (c) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Register a new bot with Nostr pubkey
|
// Register a new bot with Nostr pubkey
|
||||||
authRouter.post('/register', rateLimit(3600_000, 15), async (c) => {
|
authRouter.post('/register', rateLimit(600_000, 10), async (c) => {
|
||||||
const body = await c.req.json()
|
const body = await c.req.json()
|
||||||
const { pubkey, name, webhookUrl, archetype, profilePicUrl, customization: rawCustomization } = body
|
const { pubkey, name, webhookUrl, archetype, profilePicUrl, customization: rawCustomization } = body
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ authRouter.post('/register', rateLimit(3600_000, 15), async (c) => {
|
|||||||
|
|
||||||
|
|
||||||
// Register a human player (no webhook required)
|
// Register a human player (no webhook required)
|
||||||
authRouter.post('/register-human', rateLimit(3600_000, 15), async (c) => {
|
authRouter.post('/register-human', rateLimit(600_000, 10), async (c) => {
|
||||||
const body = await c.req.json()
|
const body = await c.req.json()
|
||||||
const { pubkey, name, profilePicUrl, avatarSeed } = body
|
const { pubkey, name, profilePicUrl, avatarSeed } = body
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user