84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
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")
|
|
}
|
|
}
|