fix(auth): compute accurate retry_after for email resend limiters #311

Merged
Claude-Run merged 1 commit from feat/BUNYIP-313-accurate-retry-after into main 2026-07-01 17:13:36 +02:00
Member

The email-verification and email-change resend limiters both returned a hardcoded retry_after of 3600 once the 3-per-hour threshold was hit, so a user who made their 3 requests 55 minutes ago was still told to wait a full hour. That value flows through to the Retry-After header and the details.retry_after body field, so it must be truthful.

Add MIN(created_at) repository queries (oldest_recent_email_verification_token, oldest_recent_email_change_request) that return the oldest in-window request time, and a pure resend_retry_after_secs(oldest, now) helper that reports the real seconds until the window frees up (oldest + window - now), clamped to >= 1. The window and threshold are now the named constants RESEND_LIMIT_WINDOW_SECS and RESEND_LIMIT_MAX (single source of truth, pub so the admin read path in BUNYIP-315 can reuse them). Throttling still triggers on the 4th request within an hour; only the reported time changed.

#BUNYIP-313

The email-verification and email-change resend limiters both returned a hardcoded retry_after of 3600 once the 3-per-hour threshold was hit, so a user who made their 3 requests 55 minutes ago was still told to wait a full hour. That value flows through to the Retry-After header and the details.retry_after body field, so it must be truthful. Add MIN(created_at) repository queries (oldest_recent_email_verification_token, oldest_recent_email_change_request) that return the oldest in-window request time, and a pure resend_retry_after_secs(oldest, now) helper that reports the real seconds until the window frees up (oldest + window - now), clamped to >= 1. The window and threshold are now the named constants RESEND_LIMIT_WINDOW_SECS and RESEND_LIMIT_MAX (single source of truth, pub so the admin read path in BUNYIP-315 can reuse them). Throttling still triggers on the 4th request within an hour; only the reported time changed. #BUNYIP-313
fix(auth): compute accurate retry_after for email resend limiters
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 24s
Check / fmt + clippy + build + tests (pull_request) Successful in 11m36s
Create release / Create release from merged PR (pull_request) Has been skipped
3c02302326
The email-verification and email-change resend limiters both returned a hardcoded retry_after of 3600 once the 3-per-hour threshold was hit, so a user who made their 3 requests 55 minutes ago was still told to wait a full hour. That value flows through to the Retry-After header and the details.retry_after body field, so it must be truthful.

Add MIN(created_at) repository queries (oldest_recent_email_verification_token, oldest_recent_email_change_request) that return the oldest in-window request time, and a pure resend_retry_after_secs(oldest, now) helper that reports the real seconds until the window frees up (oldest + window - now), clamped to >= 1. The window and threshold are now the named constants RESEND_LIMIT_WINDOW_SECS and RESEND_LIMIT_MAX (single source of truth, pub so the admin read path in BUNYIP-315 can reuse them). Throttling still triggers on the 4th request within an hour; only the reported time changed.

#BUNYIP-313
Claude-Run deleted branch feat/BUNYIP-313-accurate-retry-after 2026-07-01 17:13:36 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!311
No description provided.