This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user