You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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.
createProof builds the RFC 9449 proof: header with the jwk, the htm/htu/jti/iat/ath claims, and a JWS signed by the platform key.
thumbprint computes the RFC 7638 thumbprint via Digest.
layerSigner({ keyStore, loadOrCreateKey }) provides ManagedRelayDpopSigner. 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. sign goes through Effect's Signature via privateKeyFromCryptoKey.
Mobile: the SecureStore storage is unchanged. sign is SHA-256 via the existing expo Digest, then @noble/curvesp256.
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.ts signs with a non-extractable WebCrypto key, the same path as web, and checks the proof with shared/dpop's verifyDpopProof, the verifier the relay and server use. It covers htu normalization and the ath binding.
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 with verifyDpopProof. Mobile's tests for key persistence and the expo digest stay.
vp run check:changed passes.
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.
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.
Reviewing files that changed from the base of the PR and between 171431f and 15e9fa0.
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.ts
Client-runtime adds DPoP proof creation, a signer layer, tests for accepted proofs and invalid URLs, and a DpopProof namespace 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.ts
Mobile and web provide persisted keys through the shared signing-key interface. Their local proof-generation code and related proof tests are removed.
sequenceDiagram
participant ManagedRelayDpopSigner
participant DpopProofLayerSigner
participant PlatformKeyLoader
participant PlatformKeyStore
participant CreateProof
ManagedRelayDpopSigner->>DpopProofLayerSigner: request a DPoP proof
DpopProofLayerSigner->>PlatformKeyLoader: load or create a signing key
PlatformKeyLoader->>PlatformKeyStore: read or persist the key
DpopProofLayerSigner->>CreateProof: create proof with key and request data
CreateProof-->>ManagedRelayDpopSigner: return signed proof
Loading
Suggested reviewers:t3dotgg
Merge Risk: 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: 4
Check name
Status
Explanation
Title check
The title clearly and concisely describes the main change: sharing DPoP proof creation across the web and mobile clients.
Description check
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
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
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
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.
…bile
Web and mobile each built relay DPoP proofs: web through effect/jose,
mobile through a hand-built JWT. DpopProof in client-runtime now builds
every proof, computes the thumbprint and provides the cached signer
layer; each app supplies only its key store and an ES256 signer, WebCrypto
on web and @noble/curves on mobile, where Hermes has no SubtleCrypto.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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
size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.
1 participant
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.
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 theManagedRelayDpopSignerinterface lived inclient-runtime, so the proof format (claims,jwkheader,ath,htunormalization, 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.DpopProofKey = { publicJwk, sign(signingInput) → raw r‖s }:CryptoKey.signgoes through Effect'sSignatureviaprivateKeyFromCryptoKey.signis SHA-256 via the existing expoDigest, then@noble/curvesp256.Why the boundary sits at
signrather than at aSignatureservice: Effect's key import always runs throughcrypto.subtle.importKey, and ECDSA private keys have no raw-bytes constructor, so on Hermes there's no way to build theSignature.PrivateKeythatJwt.signneeds 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
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.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