117 lines
3.8 KiB
Go
117 lines
3.8 KiB
Go
package verification
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestServiceIssuesVerifiesAndAppliesMultiDimensionCooldown(t *testing.T) {
|
|
store, err := NewStore(8)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var code string
|
|
service, err := NewService(store, func(_ Challenge, issued string) error {
|
|
code = issued
|
|
return nil
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fixed := time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC)
|
|
service.now = func() time.Time { return fixed }
|
|
challenge, err := service.Issue(IssueRequest{Purpose: PurposeLogin, Account: "smail", Email: "user@example.com"}, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.Verify(challenge.ID, "smail", "user@example.com", code, PurposeLogin, "127.0.0.1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Issue(IssueRequest{Purpose: PurposeLogin, Account: "smail", Email: "user@example.com"}, "127.0.0.1"); err != nil {
|
|
t.Fatalf("second issue error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestServiceRequiresDelivery(t *testing.T) {
|
|
store, err := NewStore(4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := NewService(store, nil, nil); !errors.Is(err, ErrServiceDown) {
|
|
t.Fatalf("error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestServiceAppliesIndependentAccountEmailAndIPLimits(t *testing.T) {
|
|
store, err := NewStore(64)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service, err := NewService(store, func(_ Challenge, _ string) error { return nil }, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC)
|
|
service.now = func() time.Time { return now }
|
|
for i := 0; i < maxPerIPHour; i++ {
|
|
if _, err := service.Issue(IssueRequest{Purpose: PurposeLogin, Account: "user", Email: "one@example.com", TTL: time.Second}, "192.0.2.1"); err != nil {
|
|
if i == 0 || !errors.Is(err, ErrRateLimited) {
|
|
t.Fatalf("issue %d error = %v", i, err)
|
|
}
|
|
}
|
|
now = now.Add(issueInterval)
|
|
}
|
|
if _, err := service.Issue(IssueRequest{Purpose: PurposeLogin, Account: "user", Email: "two@example.com"}, "192.0.2.2"); !errors.Is(err, ErrRateLimited) {
|
|
t.Fatalf("account limit was bypassed by changing email and IP: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestServiceAppliesIPLimitAcrossAccounts(t *testing.T) {
|
|
store, err := NewStore(128)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service, err := NewService(store, func(_ Challenge, _ string) error { return nil }, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC)
|
|
service.now = func() time.Time { return now }
|
|
for i := 0; i < maxPerIPHour; i++ {
|
|
if _, err := service.Issue(IssueRequest{Purpose: PurposeLogin, Account: fmt.Sprintf("user-%d", i), Email: fmt.Sprintf("user-%d@example.com", i), TTL: time.Second}, "192.0.2.10"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now = now.Add(issueInterval)
|
|
}
|
|
if _, err := service.Issue(IssueRequest{Purpose: PurposeLogin, Account: "another", Email: "another@example.com"}, "192.0.2.10"); !errors.Is(err, ErrRateLimited) {
|
|
t.Fatalf("IP limit was bypassed by changing account and email: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestServiceBindsVerificationToResource(t *testing.T) {
|
|
store, err := NewStore(8)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var code string
|
|
service, err := NewService(store, func(_ Challenge, issued string) error {
|
|
code = issued
|
|
return nil
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
challenge, err := service.Issue(IssueRequest{Purpose: PurposeExternalEmailBind, Account: "smail", Email: "user@example.com", ResourceID: 10}, "192.0.2.30")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.VerifyBound(challenge.ID, "smail", "user@example.com", code, PurposeExternalEmailBind, 11, "192.0.2.30"); !errors.Is(err, ErrInvalidChallenge) {
|
|
t.Fatalf("cross-resource verification error = %v", err)
|
|
}
|
|
if err := service.VerifyBound(challenge.ID, "smail", "user@example.com", code, PurposeExternalEmailBind, 10, "192.0.2.30"); err != nil {
|
|
t.Fatalf("bound verification failed: %v", err)
|
|
}
|
|
}
|