Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cloud-qr-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw": minor
---

Include the Cloud home id in a linked box's pairing QR so the web app can pair through FTW Cloud.
15 changes: 6 additions & 9 deletions go/internal/appenroll/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const (

// PayloadVersionHome is v2 with a sixth segment: the home id at FTW
// Cloud, base64url of its 26 ASCII characters, so a new phone knows
// where to dial before any session with the box. No shipped app reads
// it; each parser takes exactly five segments of v2.
// where to dial before any session with the box. Both the web client
// and iPhone read this form.
PayloadVersionHome = "v3"

// PayloadOrigin is where the app is served. It is also the passkey RP
Expand All @@ -47,12 +47,9 @@ const (
MaxLANHintChars = 64
)

// QRNamesHome is the one switch that puts the home id in the QR, as v3. It
// stays false until the apps parse v3 and those builds are out: ftw-app's
// Enrollment.swift and Enrollment.kt, and ftw-webapp's enrollment.ts. A v3
// QR before then stops pairing on every shipped app. Until it flips, a new
// phone learns the home id from hello_ok.cloud on its first session.
const QRNamesHome = false
// QRNamesHome puts the Cloud home id in the QR, as v3. The web client needs
// it to pair through Cloud; iPhone also reads v3. An unlinked box keeps v2.
const QRNamesHome = true
Comment thread
frahlg marked this conversation as resolved.

// SetHome tells enrolment where to find the home id at FTW Cloud, for a QR
// that names it. fn returns "" while the box is not linked.
Expand All @@ -66,7 +63,7 @@ func (i *Identity) SetHome(fn func() string) {
//
// lanHint is where the box believes it can be reached on the local network,
// and is allowed to be empty: a box that does not know its own address should
// say nothing rather than guess, and the app falls back to the relay.
// say nothing rather than guess. The Cloud home id does not depend on it.
func (i *Identity) EnrollmentURL(code []byte, lanHint string) (string, error) {
return i.enrollmentURL(code, lanHint, QRNamesHome)
}
Expand Down
14 changes: 4 additions & 10 deletions go/internal/appenroll/payload_home_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,16 @@ func TestAnUnlinkedBoxShowsV2(t *testing.T) {
}
}

// The QR stays v2 until every shipped app parses v3: ftw-app's
// Enrollment.swift and Enrollment.kt and ftw-webapp's enrollment.ts each
// take exactly five segments today. Flipping QRNamesHome is step 2's, and
// this test is there to be changed with it.
func TestTheQRStaysV2UntilTheAppsParseV3(t *testing.T) {
if QRNamesHome {
t.Fatal("QRNamesHome is on; check that ftw-app (iOS and Android) and ftw-webapp parse v3 in builds people have, then change this test")
}
// A linked box gives a new client enough to pair with the relay blocked.
func TestTheLinkedQRNamesItsCloudHome(t *testing.T) {
id, _ := newIdentity(t)
id.SetHome(func() string { return testHome })
code, _, _ := id.MintPairingCode(apiauth.RoleOwner, PairingTTL)
url, err := id.EnrollmentURL(code, "")
if err != nil {
t.Fatal(err)
}
if strings.Contains(url, base64.RawURLEncoding.EncodeToString([]byte(testHome))) || !strings.Contains(url, "#"+PayloadVersion+".") {
t.Fatalf("the QR names the home before the apps can read it: %q", url)
if !strings.Contains(url, "."+base64.RawURLEncoding.EncodeToString([]byte(testHome))) || !strings.Contains(url, "#"+PayloadVersionHome+".") {
t.Fatalf("the linked QR omits its Cloud home: %q", url)
}
}
7 changes: 3 additions & 4 deletions go/internal/appuplink/livebox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func TestLiveBoxForTheiPhone(t *testing.T) {
defer closeTun()
uplink.Tunnel().Attach(timedTun{inner: link, log: tunLog})
bound.Store(link)
enroll.SetHome(link.Home)
enroll.OnChange(link.RosterChanged)
enroll.OnPairingCode(link.RosterChanged)
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -170,13 +171,11 @@ func TestLiveBoxForTheiPhone(t *testing.T) {
}
link.RosterChanged()
time.Sleep(300 * time.Millisecond)
v2, err := enroll.EnrollmentURL(code, "")
pairURL, err := enroll.EnrollmentURL(code, "")
if err != nil {
t.Fatal(err)
}
// v3 by hand while appenroll.QRNamesHome stays off.
v3 := strings.Replace(v2, "#v2.", "#v3.", 1) + "." + base64.RawURLEncoding.EncodeToString([]byte(link.Status().Home))
if err := os.WriteFile(filepath.Join(dir, "pair.url"), []byte(v3), 0o600); err != nil {
if err := os.WriteFile(filepath.Join(dir, "pair.url"), []byte(pairURL), 0o600); err != nil {
t.Fatal(err)
}
t.Logf("box linked as %s; pairing link written", link.Status().Home)
Expand Down
Loading