package api import ( "strings" "testing" "time" ) func TestAdminSessionTokens(t *testing.T) { a := newAdminAuth("correct horse battery") now := time.Unix(1_800_000_000, 0) tok := a.token(now) if !a.valid(tok, now) || !a.valid(tok, now.Add(adminSessionTTL-time.Second)) { t.Fatal("a fresh token should be valid for its whole lifetime") } if a.valid(tok, now.Add(adminSessionTTL)) || a.valid(tok, now.Add(24*time.Hour)) { t.Fatal("an expired token was accepted") } exp, mac, _ := strings.Cut(tok, ".") for name, bad := range map[string]string{ "empty": "", "no separator": "12345", "wrong signature": exp + "." + strings.Repeat("0", len(mac)), "extended expiry": "9999999999." + mac, // keep the signature, lengthen the life "not a number": "abc." + a.sign("abc"), "signature missing": exp + ".", } { if a.valid(bad, now) { t.Errorf("%s: forged token accepted", name) } } // Another password derives another key: tokens do not carry over. if newAdminAuth("a different password").valid(tok, now) { t.Fatal("a token signed under one password was accepted under another") } } func TestAdminPasswordCheck(t *testing.T) { a := newAdminAuth("correct horse battery") if !a.passwordOK("correct horse battery") { t.Fatal("the right password was refused") } for _, pw := range []string{"", "correct horse batter", "correct horse battery ", "CORRECT HORSE BATTERY"} { if a.passwordOK(pw) { t.Errorf("%q accepted", pw) } } } func TestAdminLockoutWindow(t *testing.T) { a := newAdminAuth("pw") now := time.Unix(1_800_000_000, 0) for i := 0; i < adminMaxFails-1; i++ { a.recordFailure("1.2.3.4", now) if _, locked := a.lockedOut("1.2.3.4", now); locked { t.Fatalf("locked out after only %d failures", i+1) } } a.recordFailure("1.2.3.4", now) wait, locked := a.lockedOut("1.2.3.4", now) if !locked || wait != adminLockout { t.Fatalf("after %d failures: locked=%v wait=%v", adminMaxFails, locked, wait) } if _, locked := a.lockedOut("5.6.7.8", now); locked { t.Fatal("one address's failures locked out another") } if _, locked := a.lockedOut("1.2.3.4", now.Add(adminLockout+time.Second)); locked { t.Fatal("the lockout never ends") } // Failures that are spread out over more than the window do not add up. b := newAdminAuth("pw") for i := 0; i < adminMaxFails+3; i++ { b.recordFailure("9.9.9.9", now.Add(time.Duration(i)*adminLockout)) } if _, locked := b.lockedOut("9.9.9.9", now.Add(time.Duration(adminMaxFails+3)*adminLockout)); locked { t.Fatal("slow guessing should not trip the lockout") } a.clear("1.2.3.4") if _, locked := a.lockedOut("1.2.3.4", now); locked { t.Fatal("a successful login should clear the failures") } }