package xfer
import (
"net/http"
"testing"
)
// countingLimit is a LoginLimit that holds a user after max misses.
type countingLimit struct {
max int
misses map[string]int
}
func (l *countingLimit) Allowed(_ *http.Request, user string) bool { return l.misses[user] < l.max }
func (l *countingLimit) Failed(_ *http.Request, user string) { l.misses[user]++ }
func (l *countingLimit) Succeeded(_ *http.Request, user string) { delete(l.misses, user) }
// A login card that does not verify against any of the account's
// credentials is a miss against that account (RFC 0021, Rate limits);
// once the limiter holds the account, even a good card proves nothing;
// a verified card clears the count.
func TestLoginCardMissesFeedTheLimiter(t *testing.T) {
f := New()
f.SetAuth(secrets{"jan": "right"})
lim := &countingLimit{max: 2, misses: map[string]int{}}
f.SetLoginLimit(lim)
try := func(secret string) loginState {
body := signedLogin("jan", secret)
return f.verifiedLogin("alpha", probe(zitUA), body, scanRequest(body))
}
if st := try("right"); st.user != "jan" {
t.Fatalf("FAIL: a good card did not verify")
}
try("wrong")
if lim.misses["jan"] != 1 {
t.Errorf("FAIL: a bad card counted %d misses, want 1", lim.misses["jan"])
}
try("wrong")
if st := try("right"); st.user != "" {
t.Errorf("FAIL: a held account still verified")
} else {
t.Log("ok: a held account's good card proves nothing")
}
delete(lim.misses, "jan")
try("wrong")
if st := try("right"); st.user != "jan" || lim.misses["jan"] != 0 {
t.Errorf("FAIL: a verified card did not clear the count: %+v, misses %d", st, lim.misses["jan"])
}
// A card for an account with no credential here is not a token miss.
body := signedLogin("kim", "anything")
f.verifiedLogin("alpha", probe(zitUA), body, scanRequest(body))
if lim.misses["kim"] != 0 {
t.Errorf("FAIL: an account with no credential was counted")
}
}