package onboarding import ( "context" "crypto/sha256" "encoding/json" "github.com/google/uuid" "net/http" "net/http/httptest" "strings" "testing" ) func TestIntegrationClaimReplayCannotReturnReplacement(t *testing.T) { f := newFixture(t) in := ClaimInput{ElderID: f.elder, Generation: f.d.Generation} id := uuid.NewString() first, err := f.s.Claim(f.ctx, f.web, id, requestJSON(in), in) must(t, err) cancel := CancelInput{ClaimID: first["claim_id"].(string)} _, err = f.s.Cancel(f.ctx, f.web, uuid.NewString(), requestJSON(cancel), cancel) must(t, err) _, err = f.s.Claim(f.ctx, f.web, uuid.NewString(), requestJSON(in), in) must(t, err) _, err = f.s.Claim(f.ctx, f.web, id, requestJSON(in), in) if err == nil { t.Fatal("returned cancelled claim from cache") } } func TestIntegrationConsumedHandoffCannotReplayReplacement(t *testing.T) { f := newFixture(t) var local string must(t, f.s.pool.QueryRow(f.ctx, `SELECT local_setup_id::text FROM device_setup_session WHERE id=$1::uuid`, f.setup).Scan(&local)) verifier := randomToken(32) sum := sha256.Sum256([]byte(verifier)) in := HandoffInput{SetupID: f.setup, LocalID: local, Challenge: encode64(sum[:])} id := uuid.NewString() first, err := f.s.Handoff(f.ctx, f.d, id, requestJSON(in), in) must(t, err) _, err = f.s.Exchange(f.ctx, ExchangeInput{Token: first["handoff_token"].(string), Verifier: verifier}) must(t, err) _, err = f.s.Handoff(f.ctx, f.d, uuid.NewString(), requestJSON(in), in) must(t, err) _, err = f.s.Handoff(f.ctx, f.d, id, requestJSON(in), in) if err == nil { t.Fatal("returned consumed handoff") } } type captureSMS struct { code string calls int } func (p *captureSMS) Send(_ context.Context, _ string, code string, _ string) error { p.code = code p.calls++ return nil } func TestIntegrationSMSLoginRateAndOneTimeCode(t *testing.T) { f := newFixture(t) sender := &captureSMS{} f.s.options.SMS = sender f.s.options.SMSLoginEnabled = true // Start a fresh anonymous browser session, as a phone opened from the device. _, err := f.s.pool.Exec(f.ctx, `UPDATE onboarding_web_session SET user_id=NULL,issuer=NULL,subject=NULL WHERE token_hash=$1`, f.web.Hash) must(t, err) f.web.UserID = "" f.web.Issuer = "" f.web.Subject = "" phone := "+8613" + strings.ReplaceAll(uuid.NewString(), "-", "")[:9] // Use decimal digits while keeping independent fixtures unique. for i, c := range phone { if c >= 'a' && c <= 'f' { phone = phone[:i] + string('0'+(c-'a')) + phone[i+1:] } } in := SMSSendInput{Phone: phone, Purpose: "LOGIN"} id := uuid.NewString() sent, err := f.s.SendSMS(f.ctx, f.web, id, in) must(t, err) _, err = f.s.SendSMS(f.ctx, f.web, id, in) must(t, err) if sender.calls != 1 { t.Fatal("SMS retry sent twice") } _, err = f.s.SendSMS(f.ctx, f.web, uuid.NewString(), in) if err == nil { t.Fatal("resend cooldown ignored") } verify := SMSVerifyInput{ChallengeID: sent["sms_challenge_id"].(string), Code: sender.code} bad := verify bad.Code = "000000" if bad.Code == sender.code { bad.Code = "999999" } _, err = f.s.VerifySMS(f.ctx, f.web, bad) if err == nil { t.Fatal("incorrect SMS accepted") } credentials, err := f.s.VerifySMS(f.ctx, f.web, verify) must(t, err) principal, err := f.s.AccountPrincipal(f.ctx, credentials.AccountToken, credentials.CSRF, true) must(t, err) if principal.Issuer != "urn:platform:onboarding:sms" { t.Fatal("incorrect SMS identity namespace") } _, err = f.s.VerifySMS(f.ctx, f.web, verify) if err == nil { t.Fatal("SMS consumed twice") } } func TestHTTPSMSGatewayContract(t *testing.T) { calls := 0 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ if r.Header.Get("Authorization") != "Bearer test-token" || r.Header.Get("Idempotency-Key") != "test-request" { t.Error("gateway authentication missing") } var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Error(err) } if body["code"] != "123456" || body["phone_e164"] != "+8613800000000" { t.Error("gateway payload incorrect") } w.WriteHeader(204) })) defer server.Close() provider, err := NewHTTPSMSProvider(server.URL, "test-token") must(t, err) provider.(*HTTPSMSProvider).client = server.Client() must(t, provider.Send(context.Background(), "+8613800000000", "123456", "test-request")) if calls != 1 { t.Fatal("unexpected request count") } }