auth: reset links pause for a day after five (audit A3)
Security audit 2026-10-17, finding A3 (low), stage 3. sendReset spaced reset mails ten minutes apart and capped them at five an hour per account, so a stranger who repeated a username kept a live 30-minute link arriving in the administrator's mailbox five times an hour, for ever. Now five reset links a day per account (resetDayLimiter); the sixth request sends one notice instead - "Reset links paused for a day", no link, saying further requests are ignored for 24 hours and that a global administrator can set a password on Server > Users - and every later one in the window sends nothing (resetNoticeLimiter), logged either way. In memory, like every limiter: a restart clears them. Test: TestResetLinksPauseForADayAfterFive. Docs: security.md, guide § Account; CHANGELOG; plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/mixeme/selfpost/internal/store"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -299,3 +300,33 @@ func TestLinkPathsWithoutAToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Five reset links a day to one account, then one notice that further
|
||||
// requests are ignored for a day, then nothing (security audit 2026-10-17,
|
||||
// A3). The ten-minute spacing is stepped over by removing the last link.
|
||||
func TestResetLinksPauseForADayAfterFive(t *testing.T) {
|
||||
m, ml := linkModule(t, true)
|
||||
u, _ := m.store.GetUserByUsername("admin")
|
||||
for i := 0; i < resetsPerDay+3; i++ {
|
||||
if err := m.store.DeleteUserLinks(u.ID, store.LinkReset); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.sendReset("admin")
|
||||
got, sent := ml.next(t)
|
||||
switch {
|
||||
case i < resetsPerDay:
|
||||
if !sent || !resetLinkRe.MatchString(got.body) {
|
||||
t.Fatalf("link %d: sent %v, %+v", i+1, sent, got)
|
||||
}
|
||||
case i == resetsPerDay:
|
||||
if !sent || got.subject != "[SelfPost] Reset links paused for a day" || resetLinkRe.MatchString(got.body) ||
|
||||
!strings.Contains(got.body, "ignored for 24 hours") {
|
||||
t.Fatalf("the notice: sent %v, %+v", sent, got)
|
||||
}
|
||||
default:
|
||||
if sent {
|
||||
t.Fatalf("request %d after the notice sent %+v", i+1, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user