perf: sync protocol and core hardening updates
This commit is contained in:
parent
152fed3b87
commit
4390ebf5a9
283 changed files with 29231 additions and 2295 deletions
240
internal/store/memory/scoped_code_state_test.go
Normal file
240
internal/store/memory/scoped_code_state_test.go
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
||||
func TestCodeStoreAtomicScopedVerification(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
newRecord := func() store.PhoneCode {
|
||||
return store.PhoneCode{
|
||||
Version: store.PhoneCodeVersionCurrent,
|
||||
Phone: "15550016021",
|
||||
Code: "12345",
|
||||
Channel: store.PhoneCodeChannelPhone,
|
||||
Purpose: store.PhoneCodePurposeChangePhone,
|
||||
UserID: 420021,
|
||||
AuthKeyID: [8]byte{1, 2, 3, 4},
|
||||
MaxAttempts: 2,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("only the active hash and exact scope can mutate", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
record := newRecord()
|
||||
if err := codes.Set(ctx, "scoped-old", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := codes.Set(ctx, "scoped-current", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result, err := codes.VerifyScoped(ctx, "scoped-old", record.Scope(), record.Code, 5); err != nil || result.Status != store.LoginCodeVerifyMissing {
|
||||
t.Fatalf("old-hash verify=%+v err=%v", result, err)
|
||||
}
|
||||
|
||||
otherScope := record.Scope()
|
||||
otherScope.AuthKeyID = [8]byte{9}
|
||||
if result, err := codes.VerifyScoped(ctx, "scoped-current", otherScope, "00000", 5); err != nil || result.Status != store.LoginCodeVerifyMissing {
|
||||
t.Fatalf("cross-scope verify=%+v err=%v", result, err)
|
||||
}
|
||||
stored, found, err := codes.Get(ctx, "scoped-current")
|
||||
if err != nil || !found || stored.Attempts != 0 {
|
||||
t.Fatalf("victim after cross-scope verify=%+v found=%v err=%v", stored, found, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong attempts preserve ttl then delete code and index", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
record := newRecord()
|
||||
if err := codes.Set(ctx, "scoped-wrong", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := codes.m["scoped-wrong"]
|
||||
first, err := codes.VerifyScoped(ctx, "scoped-wrong", record.Scope(), "00000", 9)
|
||||
if err != nil || first.Status != store.LoginCodeVerifyInvalid || first.Record.Attempts != 1 {
|
||||
t.Fatalf("first wrong=%+v err=%v", first, err)
|
||||
}
|
||||
after := codes.m["scoped-wrong"]
|
||||
if !after.expires.Equal(before.expires) || after.code.Revision == before.code.Revision {
|
||||
t.Fatalf("wrong attempt expiry/revision before=%+v after=%+v", before, after)
|
||||
}
|
||||
second, err := codes.VerifyScoped(ctx, "scoped-wrong", record.Scope(), "00000", 9)
|
||||
if err != nil || second.Status != store.LoginCodeVerifyInvalid || second.Record.Attempts != 2 {
|
||||
t.Fatalf("threshold wrong=%+v err=%v", second, err)
|
||||
}
|
||||
if _, found, _ := codes.Get(ctx, "scoped-wrong"); found {
|
||||
t.Fatal("threshold-exhausted scoped code remains")
|
||||
}
|
||||
if got := codes.scopes[record.Scope()]; got != "" {
|
||||
t.Fatalf("threshold-exhausted scope index=%q, want missing", got)
|
||||
}
|
||||
if result, err := codes.VerifyScoped(ctx, "scoped-wrong", record.Scope(), record.Code, 9); err != nil || result.Status != store.LoginCodeVerifyMissing {
|
||||
t.Fatalf("verify after exhaustion=%+v err=%v", result, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("correct code consumes both keys exactly once", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
record := newRecord()
|
||||
if err := codes.Set(ctx, "scoped-correct", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected, found, err := codes.Get(ctx, "scoped-correct")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("get expected found=%v err=%v", found, err)
|
||||
}
|
||||
accepted, err := codes.VerifyScoped(ctx, "scoped-correct", record.Scope(), record.Code, 5)
|
||||
if err != nil || accepted.Status != store.LoginCodeVerifyAccepted || accepted.Record != expected {
|
||||
t.Fatalf("accepted=%+v err=%v, want %+v", accepted, err, expected)
|
||||
}
|
||||
if _, found, _ := codes.Get(ctx, "scoped-correct"); found || codes.scopes[record.Scope()] != "" {
|
||||
t.Fatal("accepted scoped code or index remains")
|
||||
}
|
||||
if repeated, err := codes.VerifyScoped(ctx, "scoped-correct", record.Scope(), record.Code, 5); err != nil || repeated.Status != store.LoginCodeVerifyMissing {
|
||||
t.Fatalf("repeated verify=%+v err=%v", repeated, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("legacy and inconsistent records fail closed", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
legacy := newRecord()
|
||||
legacy.Version = 0
|
||||
if err := codes.Set(ctx, "scoped-legacy", legacy, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result, err := codes.VerifyScoped(ctx, "scoped-legacy", legacy.Scope(), legacy.Code, 5); err != nil || result.Status != store.LoginCodeVerifyMissing {
|
||||
t.Fatalf("legacy verify=%+v err=%v", result, err)
|
||||
}
|
||||
if _, found, _ := codes.Get(ctx, "scoped-legacy"); found || codes.scopes[legacy.Scope()] != "" {
|
||||
t.Fatal("legacy code or index remains")
|
||||
}
|
||||
|
||||
inconsistent := newRecord()
|
||||
if err := codes.Set(ctx, "scoped-inconsistent", inconsistent, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry := codes.m["scoped-inconsistent"]
|
||||
entry.code.Phone = "15550016999"
|
||||
codes.m["scoped-inconsistent"] = entry
|
||||
if result, err := codes.VerifyScoped(ctx, "scoped-inconsistent", inconsistent.Scope(), inconsistent.Code, 5); err != nil || result.Status != store.LoginCodeVerifyMissing {
|
||||
t.Fatalf("inconsistent verify=%+v err=%v", result, err)
|
||||
}
|
||||
if _, found, _ := codes.Get(ctx, "scoped-inconsistent"); found || codes.scopes[inconsistent.Scope()] != "" {
|
||||
t.Fatal("inconsistent code or selecting index remains")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCodeStoreAtomicScopedConcurrency(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const workers = 64
|
||||
newRecord := func(maxAttempts int) store.PhoneCode {
|
||||
return store.PhoneCode{
|
||||
Version: store.PhoneCodeVersionCurrent,
|
||||
Phone: "15550016022",
|
||||
Code: "12345",
|
||||
Channel: store.PhoneCodeChannelPhone,
|
||||
Purpose: store.PhoneCodePurposeChangePhone,
|
||||
UserID: 420022,
|
||||
AuthKeyID: [8]byte{5, 6, 7, 8},
|
||||
MaxAttempts: maxAttempts,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("correct verification has one winner", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
record := newRecord(7)
|
||||
if err := codes.Set(ctx, "scoped-verify-race", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statuses := concurrentMemoryScopedVerify(t, codes, "scoped-verify-race", record.Scope(), record.Code, workers)
|
||||
if statuses[store.LoginCodeVerifyAccepted] != 1 || statuses[store.LoginCodeVerifyMissing] != workers-1 || statuses[store.LoginCodeVerifyInvalid] != 0 {
|
||||
t.Fatalf("correct race statuses=%+v", statuses)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong attempts cannot be lost", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
record := newRecord(7)
|
||||
if err := codes.Set(ctx, "scoped-wrong-race", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statuses := concurrentMemoryScopedVerify(t, codes, "scoped-wrong-race", record.Scope(), "00000", workers)
|
||||
if statuses[store.LoginCodeVerifyInvalid] != 7 || statuses[store.LoginCodeVerifyMissing] != workers-7 {
|
||||
t.Fatalf("wrong race statuses=%+v", statuses)
|
||||
}
|
||||
if _, found, _ := codes.Get(ctx, "scoped-wrong-race"); found || codes.scopes[record.Scope()] != "" {
|
||||
t.Fatal("wrong race left code or scope index")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("verification and cancellation share one winner", func(t *testing.T) {
|
||||
codes := NewCodeStore()
|
||||
record := newRecord(7)
|
||||
if err := codes.Set(ctx, "scoped-mixed-race", record, time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
results := make(chan bool, workers)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func(cancel bool) {
|
||||
defer wg.Done()
|
||||
if cancel {
|
||||
_, found, err := codes.ConsumeScoped(ctx, "scoped-mixed-race", record.Scope())
|
||||
if err != nil {
|
||||
t.Errorf("ConsumeScoped: %v", err)
|
||||
}
|
||||
results <- found
|
||||
return
|
||||
}
|
||||
result, err := codes.VerifyScoped(ctx, "scoped-mixed-race", record.Scope(), record.Code, 5)
|
||||
if err != nil {
|
||||
t.Errorf("VerifyScoped: %v", err)
|
||||
}
|
||||
results <- result.Status == store.LoginCodeVerifyAccepted
|
||||
}(i%2 == 0)
|
||||
}
|
||||
wg.Wait()
|
||||
close(results)
|
||||
winners := 0
|
||||
for won := range results {
|
||||
if won {
|
||||
winners++
|
||||
}
|
||||
}
|
||||
if winners != 1 {
|
||||
t.Fatalf("verify/cancel winners=%d, want 1", winners)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func concurrentMemoryScopedVerify(t *testing.T, codes *CodeStore, hash string, scope store.PhoneCodeScope, code string, workers int) map[store.LoginCodeVerifyStatus]int {
|
||||
t.Helper()
|
||||
results := make(chan store.LoginCodeVerifyStatus, workers)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := codes.VerifyScoped(context.Background(), hash, scope, code, 5)
|
||||
if err != nil {
|
||||
t.Errorf("VerifyScoped: %v", err)
|
||||
return
|
||||
}
|
||||
results <- result.Status
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(results)
|
||||
statuses := make(map[store.LoginCodeVerifyStatus]int)
|
||||
for status := range results {
|
||||
statuses[status]++
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue