Skip to content

refactor(client-runtime): share DPoP proof creation across web and mobile - #30540

Merged
juliusmarminge merged 1 commit into
refactor/signing-key-servicesfrom
refactor/shared-dpop
Nov 4, 2026
Merged

juliusmarminge merged 1 commit into
refactor/signing-key-servicesfrom
refactor/shared-dpop

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

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):
    • 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/curves p256.

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.

Claude Opus 5.5 in Claude Code (T3 Code).

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Nov 4, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Nov 4, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

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.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
Commits

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.
Managed relay signer wiring
apps/mobile/src/features/cloud/managedRelayLayer.ts, apps/web/src/cloud/managedRelayLayer.ts
Both managed relay layers use DpopProof.layerSigner with their platform key stores and key loaders.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Sequence Diagram(s)

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: 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
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 18eb6fd and a267662.

Files selected for processing: 9
  • apps/mobile/src/features/cloud/dpop.test.ts
  • apps/mobile/src/features/cloud/dpop.ts
  • apps/mobile/src/features/cloud/managedRelayLayer.ts
  • apps/web/src/cloud/dpop.test.ts
  • apps/web/src/cloud/dpop.ts
  • apps/web/src/cloud/managedRelayLayer.ts
  • packages/client-runtime/src/relay/dpopProof.test.ts
  • packages/client-runtime/src/relay/dpopProof.ts
  • packages/client-runtime/src/relay/index.ts
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.

@gitcafe-production
gitcafe-production Bot force-pushed the refactor/shared-dpop branch 2 times, most recently from 0b9904c to 6c4e498 Compare November 4, 2026 18:06
@macroscopeapp
macroscopeapp Bot dismissed their stale review November 4, 2026 18:07

Dismissing prior approval to re-evaluate 6c4e498

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Nov 4, 2026
…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>
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Nov 4, 2026
@macroscopeapp
macroscopeapp Bot dismissed their stale review November 4, 2026 18:18

Dismissing prior approval to re-evaluate 15e9fa0

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor
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.

@juliusmarminge
juliusmarminge merged commit a07be97 into main Nov 4, 2026
32 checks passed
@juliusmarminge
juliusmarminge deleted the refactor/shared-dpop branch November 4, 2026 18:35
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Nov 5, 2026
**v0.1.8-nightly.20261104.4192** · 10 changes from 1 contributor

**2 bug fixes.**

<details>
<summary>Full changelog</summary>

## What's Changed
* fix(relay): use released drizzle rc.5 and fail installs on unmet peers by @juliusmarminge in pingdotgg/t3code#31016
* build: typecheck with tsrs and Effect's rules through @effect/tsgo by @juliusmarminge in pingdotgg/t3code#31028
* refactor: replace jose with effect/jose by @juliusmarminge in pingdotgg/t3code#30474
* refactor: move HMAC, key, and random work to effect/crypto by @juliusmarminge in pingdotgg/t3code#30501
* refactor(server): sign server tokens through PayloadSigner by @juliusmarminge in pingdotgg/t3code#30523
* refactor: hold relay signing keys in EnvironmentSigningKey and CloudMintSigner by @juliusmarminge in pingdotgg/t3code#30529
* refactor(client-runtime): share DPoP proof creation across web and mobile by @juliusmarminge in pingdotgg/t3code#30540
* fix(client-runtime): a thread keeps syncing after a stream defect by @juliusmarminge in pingdotgg/t3code#31071
* test(server): AgentSessionScanner blocked-repo test no longer races the git-read timeout by @juliusmarminge in pingdotgg/t3code#30554
* test(web): PDF preview tests wait on pdf.js loads and stop timing out by @juliusmarminge in pingdotgg/t3code#31104


**Full Changelog**: pingdotgg/t3code@v0.1.8-nightly.20261104.4179...v0.1.8-nightly.20261104.4192

</details>


Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.8-nightly.20261104.4192
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant