feat(proxy): add signer_pubkey to APNs payload (Stage A)#22
Merged
Conversation
NSE multi-account routing requires the proxy to identify which signer each push is for, since one APNs token may be registered for multiple signer pubkeys (one per account on the device). The legacy payload only had relay_url + event_id + (optionally) the embedded event, forcing NSE to either iterate every Keychain entry attempting decrypt or fall back to a single-signer assumption. Adds signer_pubkey: targetPubkey to the pushPayload object literal at the dispatch site. targetPubkey is already in scope — it's the p-tag recipient used to filter matching tokens. iOS-side, NSE reads this field via SharedKeychain.resolveSignerPubkey(userInfo:) (added in iOS Task 6) and loads the matching nsec via loadNsec(for: pubkey). Adjusts the embed gate from 3500 → 3415 to preserve the original ~300B aps-container headroom under the 4KB APNs payload cap. The new field adds ~85B (key name + 64-char hex value + JSON quoting/comma); shrinking the gate by the same amount keeps the safety margin intact. Oversized events still fall through to NSE's relay-fetch path (no regression). Forward-compatible: build 31 and earlier iOS clients ignore the new field (dict deserialization tolerates extra keys). The Stage B iOS build that consumes this field is already on feat/multi-account (commit 29ef9db). Deployed to /opt/clave-proxy-test (test proxy on port 3047, proxy-test.clave.casa). Production proxy unchanged — Stage A ships to production after Stage B iOS is verified end-to-end via the test proxy + TestFlight cohort. Verified post-deploy: clave-proxy-test.service active (running), /health endpoint returns ok=true with clean APNs counters, production proxy.clave.casa unchanged (sendOk=18072, sessionAlive=true). Plan: ~/hq/clave/plans/2026-04-30-multi-account-sprint.md iOS counterpart: feat/multi-account commit 29ef9db (Task 6) Security audit C8: ~/hq/clave/security-audits/2026-04-30-multi-account-pre-implementation.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
DocNR
added a commit
that referenced
this pull request
May 1, 2026
For internal-TestFlight build of feat/multi-account. Two changes: 1. pbxproj CURRENT_PROJECT_VERSION 31 → 32 across all 8 build configs (Clave + ClaveNSE × Debug/Release/Profile/Test). 2. SharedConstants.defaultProxyURL flipped from proxy.clave.casa → proxy-test.clave.casa. The test proxy on Dell already has Stage A (`signer_pubkey` payload field) deployed; production proxy does NOT yet (gated on Stage A PR #22 merging to main and being deployed).⚠️ This commit is TEMP. The defaultProxyURL flip MUST be reverted before merging feat/multi-account to main — at that point Stage A should already be live on production proxy, and external TestFlight builds need to point at the production URL. Suggested last-commit-on-branch: revert: flip defaultProxyURL back to production Reverts the test-proxy redirect. Stage A is now live on the production proxy; merging to main means external builds. Build: 32 (internal TestFlight only — do not promote to external without first reverting the proxy URL flip + verifying production proxy has Stage A deployed) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously hardcoded `https://proxy.clave.casa/...` in 4 expectedUrl constants (/register, /unregister, /pair-client, /unpair-client). This worked for prod but silently broke the test proxy at proxy-test.clave.casa — every NIP-98-authed request 401'd with "URL mismatch" because iOS correctly signs the URL it's calling but the proxy was validating against the prod hostname. Discovered during build 33 multi-account smoke test: zero accounts ever registered on the test proxy this whole sprint, masked by L1's foreground-only path which made current-account signing appear to work. Fix: introduce PUBLIC_PROXY_URL env var, default to the prod hostname so production deploys are unaffected. Test proxy .env adds PUBLIC_PROXY_URL=https://proxy-test.clave.casa. Backward-compatible. Build 31 external users (currently on prod) see no behavior change — default value matches the previous hardcoded constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 1, 2026
Build 33 (commit 788c495) was archived for internal-TF and burned the TestFlight build number. Bumping to 34 so we can re-archive with the HomeView scoping fix (0e2a0de) + registerAllAccounts refactor (14c7489). Internal-TF only — defaultProxyURL still points at proxy-test.clave.casa (commit 11ad90f). URL revert + another bump will be required before any external promotion, after PR #22 ships to production proxy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 2, 2026
Build 38 carries Stage C UX (Tasks 1-7): - AccountTheme palette helper (12 hash-derived gradient entries; 6 unit tests) — Task 1 - AccountStripView + SlimIdentityBar replacing the build-37 interim Menu, full-bleed gradient HomeView background — Task 2 - AddAccountSheet (generate / paste-nsec modal, current-account autoswitch on success) — Task 3 - AccountDetailView (gradient banner + petname rename + delete; Profile + Actions sections including Refresh / Rotate / Export-current-only) — Tasks 4-5 - AppState fetchProfile(for:) refactor enabling per-account refresh — Task 5 - SettingsView Accounts section + Add Account row replacing the old single-account 'Signer Key' section (orphaned state cleanup) — Task 6 - ApprovalSheet 'Connect as @account' header + ClientDetailView unpair alert with named account + PendingApprovalsView per-request signer label — Task 7 Internal-TF only. URL flip still active to test proxy. Prod rollout sequence (PR #22 to prod proxy + URL revert + final bump) gated on 2026-05-02+ user comm to existing build-31 testers per the hold from the spec brainstorm. Build green; full test suite (172 pre-sprint + 6 new AccountThemeTests) passes on iPhone 17 / iOS 26.4 simulator.
DocNR
added a commit
that referenced
this pull request
May 3, 2026
Internal-only build pointing at proxy-test.clave.casa (URL revert to prod proxy + MARKETING_VERSION 0.1.0→0.2.0 deferred to a separate commit before the external archive). Includes the 8-task AccountDetailView redesign sprint + Phase B Universal Links wiring (applinks:clave.casa entitlement + DeeplinkRouter https case + 6 unit tests). Test proxy already has Stage A signer_pubkey routing (PR #22 deployed there only); prod proxy still pending PR #22 merge. Internal smoke on test proxy validates the full feat/multi-account stack independent of the prod-proxy switch. AASA cache caveat from clave.casa coordination: Apple CDN may still serve the old single-component AASA for ~few hours. Use trailing-slash form `https://clave.casa/connect/?uri=...` for Universal Links verification today — matches both old and new AASA. The no-trailing- slash form will work after CDN refresh (within ~6h). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 3, 2026
Internal-only build pointing at proxy-test.clave.casa. URL revert to prod proxy + MARKETING_VERSION 0.1.0→0.2.0 still deferred — both flip in the build 47 external rollout commit, after PR #22 ships to prod. What's new vs build 45: - CachedProfile.name extraction (48b4fa6) - petname dropped from UI; Username row added; displayLabel chain becomes displayName → name → prefix(8) (1a130bb) - AccountDetailView banner gets rounded corners + shadow matching SlimIdentityBar's chrome — same size, just no longer squared off (46989f3) - Caption "Opens in Safari · pairs with clave.casa if needed" under the Edit on clave.casa row (2db6fa9) Also surfaced + queued: ConnectedClient identity sprint (3 iOS-side gaps that prevent Clave.Casa name from showing in Connected Clients list). Documented in BACKLOG High Priority. Not blocking external — sprint targets v0.2.1 or v0.3.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 3, 2026
…internal smoke Internal-only build for smoking the two device-test fixes from build 46: - ef1e250: AccountStripView always renders (no auto-hide at single-account) so the "+" pill stays accessible from Home for adding new accounts - 98e441c: AccountDetailView swap Form→List + .listStyle(.plain) to match Home/Activity (kills the insetGrouped extra horizontal margin) URL stays at proxy-test.clave.casa, MARKETING_VERSION stays 0.1.0. External promotion + URL revert + MARKETING_VERSION bump deferred to a later commit pending Dell→VPS prod proxy migration plan + PR #22 prod merge timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 3, 2026
Build 47 was archived to ASC; TestFlight burns build numbers permanently. Build 48 includes the swipe-delete account race fix (1729ada) that wasn't in build 47. URL stays proxy-test.clave.casa, MARKETING_VERSION stays 0.1.0 — still internal-only smoke. External rollout deferred pending PR #22 prod merge / Dell→VPS prod migration timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 3, 2026
Build 48 was archived to ASC for the swipe-delete race fix; TestFlight burns build numbers permanently. Build 49 includes the audit-5 spec- compliance fix (635bfac) for NIP-46 error responses — should resolve clave.casa's 45s timeout on stale-session recovery. URL stays proxy-test.clave.casa, MARKETING_VERSION stays 0.1.0 — still internal-only smoke. External rollout deferred pending PR #22 prod merge / Dell→VPS prod migration timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 3, 2026
Build 49 was archived to ASC for the audit-5 NIP-46 result-field fix; TestFlight burns build numbers permanently. Build 50 includes the kind:0 cross-relay merge fix (ef3821c) for "edit profile on clave.casa but Clave iOS keeps showing old fields after pull-to-refresh." URL stays proxy-test.clave.casa, MARKETING_VERSION stays 0.1.0 — still internal-only smoke. External rollout deferred pending PR #22 prod merge / Dell→VPS prod migration timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 4, 2026
- Flip defaultProxyURL: proxy-test.clave.casa → proxy.clave.casa now that PR #22 (Stage A signer_pubkey payload) is on prod. - Bump pbxproj 50 → 51. - Bump MARKETING_VERSION 0.1.0 → 0.2.0 — first external build with multi-account + Universal Links. Build 50 internal-only stays as the pre-flip diagnostic baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocNR
added a commit
that referenced
this pull request
May 4, 2026
…inks (#23) First external build with multi-account support. Highlights: - Multi-account: up to 4 accounts on one device with per-account ambient gradient identity (Stage B + Stage C). - AccountDetailView redesign (Phase A, 8 tasks) with profile + connection management and clave.casa profile-editor handoff. - Universal Links (Phase B): clave.casa Sign In QRs open directly in Clave on iOS, no scheme-squat from other clients. - audit-5 closure: NIP-46 error responses include `result: ""` per spec — web companions recover from stale sessions in <1s instead of 45s timeout. - kind:0 cross-relay merge by `created_at` — fixes stale-relay profile field bug surfaced by 5-field Profile layout. - Proxy URL flipped from proxy-test.clave.casa to proxy.clave.casa (PR #22 Stage A signer_pubkey payload merged + deployed to prod separately). pbxproj 50→51, MARKETING_VERSION 0.1.0→0.2.0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stage A of the multi-account sprint. Adds a single field to the APNs push payload so iOS NSE can route to the correct account when one device has multiple Nostr identities.
pushPayloadobject literal at line ~498.Test plan
node --check) passesfail 0/opt/clave-proxy-test/(test instance on port 3047,proxy-test.clave.casa)/healthreturnsok=truepost-deployproxy.clave.casaunaffected (verifiedsendOk=18072, sessionAlive=truepost-deploy)SharedKeychain.resolveSignerPubkey→ routes NSE wakes per-accountiOS counterpart
feat/multi-account(commit 4b633d5)SharedKeychain.resolveSignerPubkey(userInfo:)(added in Task 6)Plan + audit
~/hq/clave/plans/2026-04-30-multi-account-sprint.md~/.claude/plans/doesnt-each-account-have-dreamy-journal.md~/hq/clave/security-audits/2026-04-30-multi-account-pre-implementation.md🤖 Generated with Claude Code