Skip to content

feat(auth): sign epds_handle_mode through the callback hop - #243

Open
aspiers wants to merge 1 commit into
mainfrom
split148/handle-mode-plumbing
Open

feat(auth): sign epds_handle_mode through the callback hop#243
aspiers wants to merge 1 commit into
mainfrom
split148/handle-mode-plumbing

Conversation

@aspiers

@aspiers aspiers commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Split 3 of 4 from #148. Based on #241 — review that first; this diff shows only its own commit once #241 merges.

What it does

The chooser and consent screens need to know whether an account's handle was user-chosen or server-generated, so they can decide which identifier to show as primary (that's split 4). The mode was resolved on the way in but lost on the auth-service → pds-core hop, so the approval step could fall back to showing a generated random handle.

This carries epds_handle_mode across the hop and resolves it consistently: explicit /oauth/authorize query values take precedence, OAuth client metadata is consulted when the URL only carries a PAR request_uri, and invalid callback values are dropped rather than forwarded.

⚠️ The signed payload changes — deploy together

epds_handle_mode is signed rather than appended after signing. The parameter decides what the approval screen shows, so leaving it unsigned would let the browser flip the presentation of a flow it does not own.

That means the HMAC input changes:

request_uri \n email \n approved \n new_account \n handle \n client_id \n epds_handle_mode \n ts

auth-service and pds-core must be deployed together. In-flight callbacks signed by the old auth-service will fail verification on the new pds-core (and vice versa) for the duration of a staggered rollout. The failure mode is a rejected callback and the user retrying sign-in — not a security hole — but it is user-visible if the two services drift apart.

The empty-string sentinel convention is preserved, so the payload shape stays stable for flows that don't set the field.

Per writing-changesets, the HMAC boundary itself is contributor-facing and gets no changeset; the epds_handle_mode resolution behaviour is client-developer-facing and does.

Unrelated extraction, flagged

The commit also extracts resolveCompleteIdentity() from the /auth/complete handler. Nothing to do with handle mode — the recovery-email lookup was already nested three levels deep in a function this change had to touch anyway. Called out here rather than shipped silently; happy to drop it if you'd rather keep this PR single-purpose.

Verification

typecheck, lint, format clean; 76 test files / 1137 tests pass.

Series

  1. refactor(pds-core): extract OAuth client-id resolution from CSS middleware #241 — extract OAuth client-id resolution
  2. fix(auth): gate email sign-in until its handlers are installed #242 — gate email sign-in until handlers are installed
  3. feat(auth): sign epds_handle_mode through the callback hop #243 ← this PR — sign epds_handle_mode through the callback hop
  4. fix(pds-core): identify generated-handle accounts by email #244 — identify generated-handle accounts by email

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 5, 2026 17:09
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview Aug 6, 2026 9:58am

Request Review

@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bf37444

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@aspiers, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 21c444b8-061a-4bfa-9d16-1c603e4c8a84

📥 Commits

Reviewing files that changed from the base of the PR and between 36eab67 and bf37444.

📒 Files selected for processing (9)
  • .changeset/handle-mode-through-approval.md
  • packages/auth-service/src/__tests__/callback-handle-mode.test.ts
  • packages/auth-service/src/routes/choose-handle.ts
  • packages/auth-service/src/routes/complete.ts
  • packages/pds-core/src/__tests__/epds-callback-authorize.test.ts
  • packages/pds-core/src/index.ts
  • packages/pds-core/src/lib/epds-callback-authorize.ts
  • packages/shared/src/__tests__/crypto.test.ts
  • packages/shared/src/crypto.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@railway-app

railway-app Bot commented Aug 5, 2026

Copy link
Copy Markdown

🚅 Deployed to the ePDS-pr-243 environment in ePDS

Service Status Web Updated (UTC)
@certified-app/auth-service ✅ Success (View Logs) Web Aug 6, 2026 at 9:58 am
@certified-app/pds-core ✅ Success (View Logs) Web Aug 6, 2026 at 9:17 am
@certified-app/demo ✅ Success (View Logs) Web Aug 5, 2026 at 5:14 pm
@certified-app/demo untrusted ✅ Success (View Logs) Web Aug 5, 2026 at 5:13 pm

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR ensures epds_handle_mode (handle assignment/display mode) survives the auth-service → pds-core callback hop by including it in the HMAC-signed callback payload, and then only forwarding valid values back into the upstream /oauth/authorize redirect. This supports consistent account labelling on chooser/consent screens, including PAR-backed flows.

Changes:

  • Extend the signed epds-callback payload to include epds_handle_mode, with empty-string sentinel handling aligned with existing optional fields.
  • Add pds-core callback redirect sanitization/forwarding for epds_handle_mode (forward valid values, drop invalid).
  • Add targeted unit/integration tests in shared, auth-service, and pds-core to pin signing, verification, and forwarding behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/shared/src/crypto.ts Adds epds_handle_mode to the signed callback payload and verification payload construction.
packages/shared/src/tests/crypto.test.ts Adds coverage for signing/verifying epds_handle_mode and rejecting tampering.
packages/pds-core/src/lib/epds-callback-authorize.ts New helper to validate and forward epds_handle_mode on the post-callback /oauth/authorize redirect.
packages/pds-core/src/index.ts Threads epds_handle_mode into callback verification and uses the new authorize-redirect builder.
packages/pds-core/src/tests/epds-callback-authorize.test.ts New unit tests ensuring valid epds_handle_mode is forwarded and invalid values are dropped.
packages/auth-service/src/routes/complete.ts Signs epds_handle_mode into the callback URL and refactors identity resolution into resolveCompleteIdentity().
packages/auth-service/src/routes/choose-handle.ts Includes epds_handle_mode in the signed callback params for chosen-handle flows.
packages/auth-service/src/tests/callback-handle-mode.test.ts New integration-ish tests asserting handle mode is preserved and signatures verify across key flows.
.changeset/handle-mode-through-approval.md Adds a client-developer-facing changeset documenting epds_handle_mode resolution/behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@aspiers
aspiers force-pushed the split148/handle-mode-plumbing branch from daa7cea to 84de1ac Compare August 5, 2026 17:25
@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-243 August 5, 2026 17:25 Destroyed
@aspiers
aspiers force-pushed the split148/oauth-request-context branch from ee1ec80 to 707355b Compare August 6, 2026 09:16
@aspiers
aspiers force-pushed the split148/handle-mode-plumbing branch from 84de1ac to 14dd1b4 Compare August 6, 2026 09:16
Base automatically changed from split148/oauth-request-context to main August 6, 2026 09:56
The chooser and consent screens need to know whether the account's
handle was user-chosen or server-generated, so they can decide which
identifier to show as primary. That mode was resolved on the way in
but lost on the auth-service -> pds-core hop, so the approval step
could fall back to showing a generated random handle.

Carry epds_handle_mode across the hop, and sign it rather than
appending it afterwards: the parameter decides what the approval
screen shows, so leaving it unsigned would let the browser flip the
presentation of a flow it does not own. Invalid values are dropped at
the boundary instead of being forwarded.

Adding a field to the signed payload changes the HMAC input, so
auth-service and pds-core must be deployed together. In-flight
callbacks signed by the old auth-service will fail verification on
the new pds-core, and the user retries the sign-in.

Also extracts resolveCompleteIdentity() from the /auth/complete
handler. Unrelated to handle mode, but the recovery-email lookup was
already nested three levels deep in a function this change had to
touch anyway.

Split out of #148.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@coveralls-official

Copy link
Copy Markdown

Coverage Report for CI Build 31091281474

Coverage increased (+2.2%) to 60.533%

Details

  • Coverage increased (+2.2%) from the base build.
  • Patch coverage: 7 uncovered changes across 2 files (14 of 21 lines covered, 66.67%).
  • 2 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
packages/pds-core/src/index.ts 5 0 0.0%
packages/auth-service/src/routes/complete.ts 9 7 77.78%
Total (3 files) 21 14 66.67%

Coverage Regressions

2 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
packages/pds-core/src/index.ts 2 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 3119
Covered Lines: 1885
Line Coverage: 60.44%
Relevant Branches: 1951
Covered Branches: 1184
Branch Coverage: 60.69%
Branches in Coverage %: Yes
Coverage Strength: 9.89 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants