Step 2 of the build order. The admin mints an invite link, the recipient registers with it, and from then on has a session. - The invite is spent in the same transaction that creates the account, so a failed signup leaves the code usable - Sessions are idle timeouts, 24h or 30 days with remember me, read from a cookie or a bearer header, extended at most once a minute - Ban is a reversible toggle that drops the member's live sessions - No password minimum; login is rate limited instead, 10 failures per email in 15 minutes, cleared by a correct password - Invite codes render as links carrying ?code=, which the register form prefills; PUBLIC_URL makes them pasteable from the loopback admin panel Tests cover invite spending, the idle timeout, ban, and the rate limiter.
44 lines
1.1 KiB
Go
44 lines
1.1 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLoginRateLimit(t *testing.T) {
|
|
var l limiter
|
|
|
|
for i := range loginMaxFailures - 1 {
|
|
l.fail("[email protected]")
|
|
if l.locked("[email protected]") {
|
|
t.Fatalf("locked after %d failures, limit is %d", i+1, loginMaxFailures)
|
|
}
|
|
}
|
|
l.fail("[email protected]")
|
|
if !l.locked("[email protected]") {
|
|
t.Fatalf("not locked after %d failures", loginMaxFailures)
|
|
}
|
|
|
|
// The limit is per email: locking one address must not lock anyone else out.
|
|
if l.locked("[email protected]") {
|
|
t.Fatal("a different address was locked too")
|
|
}
|
|
|
|
// A correct password clears it, so a member who mistypes nine times and then gets it right
|
|
// starts from zero.
|
|
l.succeed("[email protected]")
|
|
if l.locked("[email protected]") {
|
|
t.Fatal("still locked after a successful login")
|
|
}
|
|
|
|
// Failures older than the window don't accumulate.
|
|
for range loginMaxFailures - 1 {
|
|
l.fail("[email protected]")
|
|
}
|
|
l.by["[email protected]"].first = time.Now().Add(-loginWindow - time.Minute)
|
|
l.fail("[email protected]")
|
|
if l.locked("[email protected]") {
|
|
t.Fatal("failures outside the window were counted")
|
|
}
|
|
}
|