refactor(client-runtime): share DPoP proof creation across web and mobile#30540
Stacked on #30529. Web and mobile each built relay DPoP proofs their own way: web through effect/jose, mobile through a hand-built JWT with its own header and payload schemas. Only the ManagedRelayDpopSigner interface lived in client-runtime, so the proof format (claims, jwk header, ath, htu normalization, thumbprint) was written twice.
What changed
client-runtime/src/relay/dpopProof.ts(DpopProof):createProofbuilds the RFC 9449 proof: header with thejwk, thehtm/htu/jti/iat/athclaims, and a JWS signed by the platform key.thumbprintcomputes the RFC 7638 thumbprint viaDigest.layerSigner({ keyStore, loadOrCreateKey })providesManagedRelayDpopSigner. It loads the key once and keeps it, and a failed load is retried.
- Platform adapters. Each app now supplies a
DpopProofKey = { publicJwk, sign(signingInput) → raw r‖s }:- Web: the IndexedDB storage is unchanged, and the key is still a non-extractable
CryptoKey.signgoes through Effect'sSignatureviaprivateKeyFromCryptoKey. - Mobile: the SecureStore storage is unchanged.
signis SHA-256 via the existing expoDigest, then@noble/curvesp256.
- Web: the IndexedDB storage is unchanged, and the key is still a non-extractable
Why the boundary sits at sign rather than at a Signature service: Effect's key import always runs through crypto.subtle.importKey, and ECDSA private keys have no raw-bytes constructor, so on Hermes there's no way to build the Signature.PrivateKey that Jwt.sign needs without a SubtleCrypto polyfill.
Stored keys and thumbprints are unchanged. Web still writes the same IndexedDB record, including the thumbprint field older versions read.
Tests
- New
dpopProof.test.tssigns with a non-extractable WebCrypto key, the same path as web, and checks the proof withshared/dpop'sverifyDpopProof, the verifier the relay and server use. It covershtunormalization and theathbinding. - Removed duplicates: the web proof test and the two mobile proof-signing unit tests. Mobile's end-to-end signing is still covered by
remoteRegistration.test, which verifies a real mobile-signed proof withverifyDpopProof. Mobile's tests for key persistence and the expo digest stay. vp run check:changedpasses.
Not yet done: a live relay sign-in on a device. It needs a real T3 Connect account, so it's left for a manual pass.
Claude Opus 5.5 in Claude Code (T3 Code).
🤖 Generated with Claude Code
Checks
Approvability
Verdict: Approved at 15e9fa0
Macroscope's review found this PR approvable — This consolidates DPoP proof creation and signing services while preserving existing keys, token policies, and platform storage. The assessed changes introduce no material access-policy or operational change requiring human judgment.
Notes:
- All code in this push has already been reviewed. Approvability was decided on eligibility alone.
You can add or adjust custom eligibility rules. Learn more.
Important
Review skipped
Review was skipped as selected files did not have any reviewable changes.
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: refactor/shared-dpop
- File: .coderabbit.config.ts
- Commit: 15e9fa0
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
c39b078e-46d0-443c-a38d-86c9e5053343
External image Commits
Reviewing files that changed from the base of the PR and between 171431f52276851030d859356db06812a2b8a07a and 15e9fa052c1c756a8374259095ffca6d069cb288.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
- Trigger review
No actionable comments were generated in the recent review.
Walkthrough
Client-runtime now provides shared DPoP proof creation and signer logic. Mobile and web supply platform-specific persisted key loaders and use the shared signer in their managed relay layers.
Changes: DPoP proof generation and relay signing
Layer / File(s) Summary Shared proof creation and signer
packages/client-runtime/src/relay/dpopProof.ts,packages/client-runtime/src/relay/dpopProof.test.ts,packages/client-runtime/src/relay/index.tsClient-runtime adds DPoP proof creation, a signer layer, tests for accepted proofs and invalid URLs, and a DpopProofnamespace export.Mobile and browser key adapters
apps/mobile/src/features/cloud/dpop.ts,apps/mobile/src/features/cloud/dpop.test.ts,apps/web/src/cloud/dpop.ts,apps/web/src/cloud/dpop.test.tsMobile and web provide persisted keys through the shared signing-key interface. Their local proof-generation code and related proof tests are removed. Managed relay signer wiring
apps/mobile/src/features/cloud/managedRelayLayer.ts,apps/web/src/cloud/managedRelayLayer.tsBoth managed relay layers use DpopProof.layerSignerwith their platform key stores and key loaders.Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Refactor
Sequence Diagram(s)
mermaid1 sequenceDiagram 2 participant ManagedRelayDpopSigner 3 participant DpopProofLayerSigner 4 participant PlatformKeyLoader 5 participant PlatformKeyStore 6 participant CreateProof 7 ManagedRelayDpopSigner->>DpopProofLayerSigner: request a DPoP proof 8 DpopProofLayerSigner->>PlatformKeyLoader: load or create a signing key 9 PlatformKeyLoader->>PlatformKeyStore: read or persist the key 10 DpopProofLayerSigner->>CreateProof: create proof with key and request data 11 CreateProof-->>ManagedRelayDpopSigner: return signed proof Suggested reviewers:
t3dotgg
Merge Risk: External image Minimal · previous review a2676
This refactor moves DPoP proof creation into a shared module while keeping platform key storage and signing in each app. It should not change behavior for existing users. A manual relay sign-in check on a real device is still worth doing before release, but nothing found blocks the merge.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title clearly and concisely describes the main change: sharing DPoP proof creation across the web and mobile clients. Description check Passed The description explains the problem, implementation, affected platforms, tests, and the untested live sign-in scenario. It references the stacked pull request for scope, although explicit maintainer … Linked Issues check Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request. Full details: Description check
Explanation
The description explains the problem, implementation, affected platforms, tests, and the untested live sign-in scenario. It references the stacked pull request for scope, although explicit maintainer approval is not stated.
Finishing Touches
Generate docstrings
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: refactor/shared-dpop
- File: .coderabbit.config.ts
- Commit: a267662
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
148975dc-00ba-47de-8b73-7d1bffd73cad
External image Commits
Reviewing files that changed from the base of the PR and between 18eb6fd158485abd8b767690601372e7206b7265 and a267662e206b76921c0a59fe946767d81851ae1c.
External image Files selected for processing: 9
apps/mobile/src/features/cloud/dpop.test.tsapps/mobile/src/features/cloud/dpop.tsapps/mobile/src/features/cloud/managedRelayLayer.tsapps/web/src/cloud/dpop.test.tsapps/web/src/cloud/dpop.tsapps/web/src/cloud/managedRelayLayer.tspackages/client-runtime/src/relay/dpopProof.test.tspackages/client-runtime/src/relay/dpopProof.tspackages/client-runtime/src/relay/index.ts
External image Files with no reviewable changes: 1
- apps/web/src/cloud/dpop.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: refactor/shared-dpop
- File: .coderabbit.config.ts
- Commit: a267662
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
148975dc-00ba-47de-8b73-7d1bffd73cad
External image Commits
Reviewing files that changed from the base of the PR and between 18eb6fd158485abd8b767690601372e7206b7265 and a267662e206b76921c0a59fe946767d81851ae1c.
External image Files selected for processing: 9
apps/mobile/src/features/cloud/dpop.test.tsapps/mobile/src/features/cloud/dpop.tsapps/mobile/src/features/cloud/managedRelayLayer.tsapps/web/src/cloud/dpop.test.tsapps/web/src/cloud/dpop.tsapps/web/src/cloud/managedRelayLayer.tspackages/client-runtime/src/relay/dpopProof.test.tspackages/client-runtime/src/relay/dpopProof.tspackages/client-runtime/src/relay/index.ts
External image Files with no reviewable changes: 1
- apps/web/src/cloud/dpop.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.
Added 1 commit
- refactor(client-runtime): share DPoP proof creation across web and mobile15e9fa0
@coderabbitai review
Action not completed
No files to review.
Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.