feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
@@ -0,0 +1,425 @@
package onboarding
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"image"
_ "image/jpeg"
_ "image/png"
"net/http"
"net/http/httptest"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
func TestLocalSlideRenderAndBounds(t *testing.T) {
for i := 0; i < 8; i++ {
out, target, err := renderSlideCaptcha()
must(t, err)
if out["mode"] != "slide" || out["thumb_x"] != 5 || out["thumb_y"] != target.Y || target.X < 80 || target.X > 258 {
t.Fatal("invalid slide geometry")
}
for _, key := range []string{"x", "y", "target_x", "target_y", "solution", "answer_hash"} {
if _, exists := out[key]; exists {
t.Fatal("solution exposed")
}
}
for _, part := range []struct {
key, prefix string
w, h int
}{
{"image", "data:image/jpeg;base64,", slideWidth, slideHeight},
{"thumb_image", "data:image/png;base64,", slideTileSize, slideTileSize},
} {
encoded := out[part.key].(string)
if !strings.HasPrefix(encoded, part.prefix) {
t.Fatal("unexpected image format")
}
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(encoded, part.prefix))
must(t, err)
img, _, err := image.Decode(bytes.NewReader(raw))
must(t, err)
if img.Bounds().Dx() != part.w || img.Bounds().Dy() != part.h {
t.Fatal("incorrect image bounds")
}
if len(raw) > 200000 {
t.Fatal("unbounded captcha payload")
}
}
}
}
func TestLocalSlideParallelRenderer(t *testing.T) {
var wg sync.WaitGroup
var rendered atomic.Int32
for i := 0; i < 16; i++ {
wg.Add(1)
go func() {
defer wg.Done()
out, _, err := renderSlideCaptcha()
if err == nil {
if out["mode"] != "slide" {
t.Error("mode")
}
rendered.Add(1)
return
}
if e, ok := err.(*Error); !ok || e.Code != "CAPTCHA_BUSY" {
t.Error(err)
}
}()
}
wg.Wait()
if rendered.Load() < 1 {
t.Fatal("no render completed")
}
}
func TestLocalSlideDecoyGeometry(t *testing.T) {
for x := 80; x <= 258; x++ {
positions, err := slideGapPositions(slideSolution{X: x, Y: 20})
must(t, err)
if len(positions) != 3 || positions[0] != x {
t.Fatal("target lost")
}
for i, gap := range positions {
if gap < 10 || gap > 258 {
t.Fatal("gap outside image")
}
for _, other := range positions[:i] {
if gap-other < slideTileSize+10 && other-gap < slideTileSize+10 {
t.Fatal("overlapping gaps")
}
}
}
}
}
func newSlideLoginFixture(t *testing.T) *loginFixture {
f := newLoginFixture(t)
setCaptchaTestPolicy(t, f.fixture, "slide", "image")
return f
}
func (f *loginFixture) slideChallenge() (map[string]any, slideSolution) {
f.t.Helper()
w, ch := f.request("POST", "/api/v1/web/login/captcha", struct{}{}, f.cookie, f.csrf, "")
if w.Code != 200 || ch["mode"] != "slide" {
f.t.Fatal("slide generation failed", w.Body.String())
}
value, err := f.s.redis.Get(f.ctx, f.s.captchaKey(digest([]byte(f.cookie.Value)))).Result()
must(f.t, err)
var stored storedCaptcha
must(f.t, json.Unmarshal([]byte(value), &stored))
plain, err := f.s.crypt.open(stored.Solution, slideAAD(digest([]byte(f.cookie.Value)), stored))
must(f.t, err)
var target slideSolution
must(f.t, json.Unmarshal(plain, &target))
return ch, target
}
func slideInput(ch map[string]any, target slideSolution) map[string]any {
return map[string]any{"captcha_id": ch["captcha_id"], "x": target.X, "y": target.Y}
}
func TestLocalSlideSuccessTicketSMSAndReplay(t *testing.T) {
f := newSlideLoginFixture(t)
provider := &fakeCaptchaProvider{}
f.s.options.Captcha = provider
ch, target := f.slideChallenge()
w, proof := f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 || proof["captcha_id"] != ch["captcha_id"] || proof["expires_at_ms"] != ch["expires_at_ms"] {
t.Fatal("valid position rejected or lifetime extended", w.Body.String())
}
ticket := proof["captcha_ticket"].(string)
if !validB64(ticket, 32) {
t.Fatal("insecure proof")
}
value, err := f.s.redis.Get(f.ctx, f.s.captchaKey(digest([]byte(f.cookie.Value)))).Result()
must(t, err)
var stored storedCaptcha
must(t, json.Unmarshal([]byte(value), &stored))
if len(stored.Solution) != 0 || stored.ProofHash == "" || strings.Contains(value, ticket) {
t.Fatal("proof must be hashed and solution removed")
}
w, _ = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 401 {
t.Fatal("position replay accepted")
}
input := SMSSendInput{Phone: f.phone, Purpose: "LOGIN", CaptchaID: ch["captcha_id"].(string), CaptchaTicket: ticket}
w, _ = f.request("POST", "/api/v1/web/login/sms/send", input, f.cookie, f.csrf, "")
if w.Code != 200 || f.sender.calls != 1 || provider.calls != 0 {
t.Fatal("local proof did not authorize SMS independently", w.Body.String())
}
w, _ = f.request("POST", "/api/v1/web/login/sms/send", input, f.cookie, f.csrf, "")
if w.Code != 401 || f.sender.calls != 1 {
t.Fatal("proof reused")
}
}
func TestLocalSlidePasswordUsesLocalProof(t *testing.T) {
f := newLoginFixture(t)
account, csrf := f.smsAccount()
w, _ := f.request("POST", "/api/v1/web/account/password", SetPasswordInput{Password: "LocalOnly123!"}, account, csrf, "")
if w.Code != 200 {
t.Fatal(w.Body.String())
}
setCaptchaTestPolicy(t, f.fixture, "slide", "image")
provider := &fakeCaptchaProvider{}
f.s.options.Captcha = provider
f.newLoginSession()
ch, target := f.slideChallenge()
w, proof := f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal(w.Body.String())
}
w, _ = f.request("POST", "/api/v1/web/login/password", PasswordLoginInput{Phone: f.phone, Password: "LocalOnly123!", CaptchaID: ch["captcha_id"].(string), CaptchaTicket: proof["captcha_ticket"].(string)}, f.cookie, f.csrf, "")
if w.Code != 200 || provider.calls != 0 {
t.Fatal("password did not use local proof", w.Body.String())
}
}
func TestLocalSlidePolicyAuditAndMiniRestriction(t *testing.T) {
f := newFixture(t)
setCaptchaTestPolicy(t, f, "image", "image")
f.s.options.TokenVerifier = captchaPolicyToken{f.p}
policy, err := f.s.readCaptchaPolicy(f.ctx, f.s.pool)
must(t, err)
call := func(in captchaPolicy) (any, error) {
raw, _ := json.Marshal(in)
r := httptest.NewRequest("POST", "https://platform.example/api/v1/ops/login-captcha-policy", bytes.NewReader(raw))
r.Header.Set("Authorization", "Bearer test")
r.Header.Set("Content-Type", "application/json")
r.Header.Set("X-Tenant-ID", f.tenantPublic)
return f.s.captchaPolicyHTTP(r, uuid.NewString())
}
if _, err = call(captchaPolicy{SaaS: "slide", Mini: "slide", Version: policy.Version}); err == nil {
t.Fatal("mini slide enabled before release")
}
out, err := call(captchaPolicy{SaaS: "slide", Mini: "image", Version: policy.Version})
must(t, err)
if out.(map[string]any)["saas_mode"] != "slide" {
t.Fatal("slide policy not persisted")
}
var count int
must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM audit_event WHERE tenant_id=$1 AND action='login.captcha.policy.update'`, f.tenant).Scan(&count))
if count != 1 {
t.Fatal("slide switch missing audit")
}
if _, err = call(captchaPolicy{SaaS: "image", Mini: "image", Version: policy.Version}); err == nil {
t.Fatal("stale policy accepted")
}
}
func TestLocalSlideWrongPositionConsumedAndRefreshProtected(t *testing.T) {
f := newSlideLoginFixture(t)
ch, target := f.slideChallenge()
wrong := target
wrong.X += 20
w, _ := f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, wrong), f.cookie, f.csrf, "")
if w.Code != 401 {
t.Fatal("wrong position accepted")
}
w, _ = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 401 {
t.Fatal("failed challenge reused")
}
old, oldTarget := f.slideChallenge()
current, currentTarget := f.slideChallenge()
w, _ = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(old, oldTarget), f.cookie, f.csrf, "")
if w.Code != 401 {
t.Fatal("refreshed challenge accepted")
}
w, _ = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(current, currentTarget), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal("old request destroyed fresh challenge")
}
key := f.s.captchaKey(digest([]byte(f.cookie.Value)))
oldValue, err := f.s.redis.Get(f.ctx, key).Result()
must(t, err)
f.slideChallenge()
updated, err := f.s.redis.Eval(f.ctx, replaceSlideRecord, []string{key}, oldValue, "invalid overwrite").Int64()
must(t, err)
if updated != 0 {
t.Fatal("delayed proof overwrote a refresh")
}
}
func TestLocalSlideExpiryPolicyClientAndForgery(t *testing.T) {
f := newSlideLoginFixture(t)
hash := digest([]byte(f.cookie.Value))
ch, target := f.slideChallenge()
must(t, f.s.redis.PExpire(f.ctx, f.s.captchaKey(hash), -time.Second).Err())
w, _ := f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 401 {
t.Fatal("expired challenge accepted")
}
ch, target = f.slideChallenge()
w, proof := f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal(w.Body.String())
}
mini := context.WithValue(f.ctx, captchaRequestKey{}, captchaRequestInfo{client: "mini_program"})
if f.s.verifyLoginCaptcha(mini, hash, ch["captcha_id"].(string), "", proof["captcha_ticket"].(string), "") == nil {
t.Fatal("cross-client proof accepted")
}
ch, target = f.slideChallenge()
w, proof = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal(w.Body.String())
}
_, err := f.s.pool.Exec(f.ctx, `UPDATE login_captcha_policy SET version=version+1`)
must(t, err)
if f.s.verifyLoginCaptcha(f.ctx, hash, ch["captcha_id"].(string), "", proof["captcha_ticket"].(string), "") == nil {
t.Fatal("old-policy proof accepted")
}
ch, _ = f.slideChallenge()
if f.s.verifyLoginCaptcha(f.ctx, hash, ch["captcha_id"].(string), "", randomToken(32), "") == nil {
t.Fatal("unverified position authorized proof")
}
ch, target = f.slideChallenge()
w, proof = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal(w.Body.String())
}
must(t, f.s.redis.PExpire(f.ctx, f.s.captchaKey(hash), -time.Second).Err())
if f.s.verifyLoginCaptcha(f.ctx, hash, ch["captcha_id"].(string), "", proof["captcha_ticket"].(string), "") == nil {
t.Fatal("expired proof accepted")
}
}
func TestLocalSlideHTTPOriginCSRFSessionAndStrictCoordinates(t *testing.T) {
f := newSlideLoginFixture(t)
ch, target := f.slideChallenge()
w, _ := f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, "wrong", "")
if w.Code != 403 {
t.Fatal("invalid CSRF accepted")
}
w, session := f.request("GET", "/api/v1/web/login/session", nil, nil, "", "")
other := w.Result().Cookies()[0]
w, _ = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), other, session["csrf_token"].(string), "")
if w.Code != 401 {
t.Fatal("cross-session position accepted")
}
call := func(raw, origin string) int {
r := httptest.NewRequest("POST", "https://platform.example/api/v1/web/login/captcha/verify", strings.NewReader(raw))
r.RemoteAddr = f.ip
r.AddCookie(f.cookie)
r.Header.Set("Origin", origin)
r.Header.Set("Content-Type", "application/json")
r.Header.Set("X-CSRF-Token", f.csrf)
r.Header.Set("X-Request-ID", uuid.NewString())
w := httptest.NewRecorder()
f.mux.ServeHTTP(w, r)
return w.Code
}
raw, _ := json.Marshal(slideInput(ch, target))
if call(string(raw), "https://other.example") != 403 {
t.Fatal("cross-origin accepted")
}
for _, bad := range []string{
`{"captcha_id":"` + ch["captcha_id"].(string) + `","x":1.5,"y":10}`,
`{"captcha_id":"` + ch["captcha_id"].(string) + `","x":10}`,
`{"captcha_id":"` + ch["captcha_id"].(string) + `","x":null,"y":10}`,
`{"captcha_id":"` + ch["captcha_id"].(string) + `","x":10,"y":10,"mode":"image"}`,
} {
if call(bad, "https://platform.example") != 400 {
t.Fatal("malformed coordinates accepted")
}
}
w, _ = f.request("POST", "/api/v1/web/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal("unauthorized request consumed challenge")
}
}
func TestLocalSlideConcurrentProofAndConsumeOnce(t *testing.T) {
f := newSlideLoginFixture(t)
ch, target := f.slideChallenge()
x, y := target.X, target.Y
hash := digest([]byte(f.cookie.Value))
var wg sync.WaitGroup
var winners atomic.Int32
var ticket string
var ticketMu sync.Mutex
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
out, err := f.s.verifySlideCaptcha(f.ctx, hash, SlideCaptchaVerifyInput{ID: ch["captcha_id"].(string), X: &x, Y: &y}, func(tx pgx.Tx) error { return f.s.checkLoginSession(f.ctx, tx, hash) })
if err == nil {
winners.Add(1)
ticketMu.Lock()
ticket = out["captcha_ticket"].(string)
ticketMu.Unlock()
}
}()
}
wg.Wait()
if winners.Load() != 1 {
t.Fatal("concurrent position generated multiple proofs")
}
winners.Store(0)
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if f.s.verifyLoginCaptcha(f.ctx, hash, ch["captcha_id"].(string), "", ticket, "") == nil {
winners.Add(1)
}
}()
}
wg.Wait()
if winners.Load() != 1 {
t.Fatal("concurrent proof consumed more than once")
}
}
func TestLocalSlideOperatorAndSetupRoutes(t *testing.T) {
f := newOperatorLoginFixture(t)
setCaptchaTestPolicy(t, f.fixture, "slide", "image")
w, ch := f.request("POST", "/api/v1/web/operator/login/captcha", struct{}{}, f.cookie, f.csrf, "")
if w.Code != 200 || ch["mode"] != "slide" {
t.Fatal("operator slide unavailable")
}
hash := digest([]byte(f.cookie.Value))
value, err := f.s.redis.Get(f.ctx, f.s.captchaKey(hash)).Result()
must(t, err)
var stored storedCaptcha
must(t, json.Unmarshal([]byte(value), &stored))
plain, err := f.s.crypt.open(stored.Solution, slideAAD(hash, stored))
must(t, err)
var target slideSolution
must(t, json.Unmarshal(plain, &target))
w, _ = f.request("POST", "/api/v1/web/operator/login/captcha/verify", slideInput(ch, target), f.cookie, f.csrf, "")
if w.Code != 200 {
t.Fatal("operator position rejected", w.Body.String())
}
setup := newSlideLoginFixture(t)
setup.s.options.Enabled = true
token, csrf := randomToken(32), randomToken(32)
_, err = setup.s.pool.Exec(setup.ctx, `UPDATE onboarding_web_session SET token_hash=$1,csrf_hash=$2 WHERE token_hash=$3`, digest([]byte(token)), digest([]byte(csrf)), setup.web.Hash)
must(t, err)
cookie := &http.Cookie{Name: setup.s.cookieName(false), Value: token}
w, ch = setup.request("POST", "/api/v1/web/auth/captcha", struct{}{}, cookie, csrf, "")
if w.Code != 200 || ch["mode"] != "slide" {
t.Fatal("setup slide unavailable", w.Body.String())
}
hash = digest([]byte(token))
value, err = setup.s.redis.Get(setup.ctx, setup.s.captchaKey(hash)).Result()
must(t, err)
must(t, json.Unmarshal([]byte(value), &stored))
plain, err = setup.s.crypt.open(stored.Solution, slideAAD(hash, stored))
must(t, err)
must(t, json.Unmarshal(plain, &target))
w, _ = setup.request("POST", "/api/v1/web/auth/captcha/verify", slideInput(ch, target), cookie, csrf, "")
if w.Code != 200 {
t.Fatal("setup position rejected", w.Body.String())
}
}