fix(api): rate-limit email-OTP sends to close the email-bomb vector
handleEmailOTPStart minted and mailed a code on every call, so an authenticated caller could drive unbounded mail to any address they typed — an email-bomb primitive against arbitrary mailboxes. Add a separate otpLimiter (its own sync.Once and map, distinct from the wake limiter) and throttle each send on two keys before anything is minted: the caller (user:<id>) and the recipient (email:<lower>). A refused send mints no code and mails nothing; both cooldowns are recorded only after delivery succeeds, mirroring the wake path so a failed mint or delivery never consumes the throttle. The two-key design stops both one account fanning out across addresses and many accounts converging on one mailbox.
This commit is contained in:
3 files changed
+115
No files matched your search
@@ -95,6 +95,9 @@ type API struct {
|
||||
|
||||
cooldownOnce sync.Once
|
||||
cooldown *cooldownLimiter
|
||||
|
||||
otpCooldownOnce sync.Once
|
||||
otpCooldown *cooldownLimiter
|
||||
}
|
||||
|
||||
// now returns the current time using the injected clock.
|
||||
@@ -113,6 +116,17 @@ func (a *API) limiter() *cooldownLimiter {
|
||||
return a.cooldown
|
||||
}
|
||||
|
||||
// otpLimiter lazily builds a SEPARATE cooldown limiter for email-OTP sends, so an
|
||||
// OTP resend throttle never shares state with the wake throttle. Keyed by
|
||||
// principal and by recipient (see handleEmailOTPStart), it bounds how often a code
|
||||
// may be mailed and closes the email-bomb vector.
|
||||
func (a *API) otpLimiter() *cooldownLimiter {
|
||||
a.otpCooldownOnce.Do(func() {
|
||||
a.otpCooldown = &cooldownLimiter{now: a.now, last: map[string]time.Time{}}
|
||||
})
|
||||
return a.otpCooldown
|
||||
}
|
||||
|
||||
// apiRoute is one served HTTP route. Each face exposes its routes as a single
|
||||
// table (internalAPIRoutes / externalAPIRoutes) so that one declaration drives
|
||||
// BOTH handler construction here AND the OpenAPI parity test (openapi_test.go):
|
||||
|
||||
Reference in new issue
Block a user