feat: link enrolled controllers to a site - #40
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 📝 WalkthroughWalkthroughThe change exposes controller generation data from kept enrolments, adds cloud APIs for listing and creating sites and linking controllers, and adds a sites screen to the signed-in iOS account view. The screen displays sites and paired controllers and supports site creation and controller linking. Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Severity of issue fixed: Medium Merge Risk: 🔵 Low · up to If sites fail to load, the screen can appear stuck loading without an obvious retry action. This is a bounded issue that should be fixed or accepted before merging. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Linking is limited to a signed-in account and does not send pairing secrets. The remaining risk is whether the cloud verifies who may claim a controller generation, and whether account changes during an in-flight request leave the app showing the right account’s result. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
b562a98 to
a7e90da
Compare
a7e90da to
d1ad7b5
Compare
There was a problem hiding this comment.
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (1)
apps/ios/Origin89/SitesView.swift-115-123 (1)
115-123: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the site list visible after a failed reload
reload()setsfailurewhencloud.sites()fails. It leavessitesunchanged. On the first load,sitesis still nil. The screen then shows the error and aProgressViewthat never ends. The person can only retry by pull-to-refresh, and nothing on screen says that is possible. Two fixes are possible. You can show a retry button in the failure section. You can also stop showing the spinner oncefailureis set.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Advanced
Run ID: f301eddf-36f1-4935-bda0-f78e303d3657
📒 Files selected for processing (11)
apps/ios/Origin89/AccountView.swiftapps/ios/Origin89/SetupView.swiftapps/ios/Origin89/SitesView.swiftapps/ios/SetupBench/Sources/SetupBench/main.swiftapps/ios/SetupKit/Sources/SetupKit/AccountEnrolmentStore.swiftapps/ios/SetupKit/Sources/SetupKit/Contract.swiftapps/ios/SetupKit/Sources/SetupKit/SetupFlow.swiftapps/ios/SetupKit/Sources/SetupKit/Sites.swiftapps/ios/SetupKit/Tests/SetupKitTests/GenerationTests.swiftapps/ios/SetupKit/Tests/SetupKitTests/KeptEnrolmentTests.swiftapps/ios/SetupKit/Tests/SetupKitTests/SitesTests.swift
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.
Closes #35. Stacked on #38, which adds the cloud client and the fresh sign-in path; merge that first.
Change
A signed-in account can now link the controllers this phone is paired with to a site it owns. Account › Sites lists the account's sites and the pairings this account sees on the phone, marks which are linked, creates a site, and links a controller under a name the person can edit. The link request carries only
deviceId,epochand the name, never the setup code or the enrolment key. When the cloud answersreauthentication_required, the app signs in again through AuthKit and retries once, only if the same account is still signed in before and after that sign-in.kept_generation(UniFFI) returns thedevice_idandepochof a kept enrolment and clears the bytes. km43'sStoredEnrolment::decodevalidates them first; km43 exposes neither field, so both are read from its documented encoding layout.EnrolmentStoregainsstoredDeviceIDs() throws.AccountEnrolmentStorelists its own and the signed-out controllers once each, andgenerations(reader:)drops entries that do not decode or are filed under another controller. A Keychain that cannot be read, as on a locked phone, shows as an error, not as no controllers.CloudClient.sites(),createSite(named:)andlink(_:named:to:authenticate:)follow the@origin89/cloud0.1.0 contract. Site IDs must be UUIDs because they go back into request paths, and names are 1 to 80 characters once trimmed.Trust boundary
Site authority and generation provenance are the cloud's to enforce; this PR only supplies identifiers and the bearer token. From the cloud code at origin89hq/cloud@f554459 (not tested against the deployed Worker): the link insert requires an
ownermembership of the site for the token's user (apps/cloud/src/store.ts), and cloud#4 reports admin and non-member tests answering 404. Provenance is not enforced: the cloud cannot check that the caller is enrolled with the controller, so anyone who has seen adevice_idand epoch can claim that generation first and block the owner's link with 409. That is origin89hq/cloud#3, open, and it must be fixed before the cloud accepts readings. A link grants no controller access; the controller stays the only authority.Sites shows only in builds with a cloud. Only production is deployed, so Debug builds, which sign in to the staging WorkOS environment, have none until a staging Worker exists.
Validation
just checkpasses: rustfmt, Clippy, Rust tests, 212 Swift tests, the unsigned simulator build with no warnings, and the bench build.u32::MAX, and undecodable bytes (empty, cut, too long, unknown format, zero epoch).generation_linked,stale_epochandnot_found, a fresh sign-in then retry, a cancelled sign-in, a second stale answer that is not retried, a sign-in as another account that links nothing, a signed-out account, and an unreadable pairing list.Not done: no link has been made against
cloud.origin89.comfrom a device. That needs a Release build signed in with a production WorkOS account.