Skip to content

Fix OAuth account enrichment with tooltip explanation#148

Open
Kzoeps wants to merge 17 commits intomainfrom
143-oauth-enrichment-cover-all-pages-hide-handles-in-random-mode-explain-via-tooltip
Open

Fix OAuth account enrichment with tooltip explanation#148
Kzoeps wants to merge 17 commits intomainfrom
143-oauth-enrichment-cover-all-pages-hide-handles-in-random-mode-explain-via-tooltip

Conversation

@Kzoeps
Copy link
Copy Markdown
Contributor

@Kzoeps Kzoeps commented May 4, 2026

Summary

This PR restores account identity enrichment across the OAuth and account-management surfaces for ePDS deployments that use generated/random handles.
The user-facing goal is simple: when a handle is system-generated and not meaningful to the user, ePDS should identify the account primarily by email while still keeping the public AT Protocol handle available where it is useful for context, accessibility, or app interoperability.
closes #143

What changed

  • Restores scoped account identity enrichment on:

    • OAuth account chooser pages
    • OAuth consent / approval pages
    • /account
    • /account/:did
    • preview chooser and preview consent routes
  • Makes random-handle flows email-first:

    • chooser rows show email as the visible primary identifier
    • underlying handles are preserved through accessible descriptions
    • consent pages show email as the primary account identifier
    • public handles remain available through the identity information tooltip
  • Tightens the DOM enrichment logic so only known account identity elements are rewritten.
    This avoids accidentally mutating legal copy, technical details, connected-app rows, device rows, footer text, or arbitrary prose that happens to contain handle-like text.

  • Makes epds_handle_mode resolution consistent between auth-service and pds-core:

    • explicit authorize/callback query values take precedence
    • OAuth client metadata is used when the authorize URL only has a PAR request_uri
    • valid handle mode is preserved through /oauth/epds-callback
    • invalid callback values are ignored instead of forwarded
  • Shares OAuth request-context resolution between chooser enrichment and client CSS injection so PAR-backed authorize pages can still resolve the client id correctly.

  • Updates random-handle e2e coverage to assert the accessible hidden-handle behavior instead of the previous stale title tooltip behavior.

Summary by CodeRabbit

  • New Features

    • Email shown consistently as the primary account identifier across sign-in, consent, chooser, and account pages; consent pages now include an identity tooltip linking email and public handle.
    • Email sign-in button waits for page readiness before accepting clicks.
  • Bug Fixes

    • Prevents brief exposure of generated random handles during final approval.
  • Accessibility

    • Hidden handles exposed via accessible descriptions (aria-describedby) instead of title-only tooltips.
  • Tests

    • Added E2E and unit tests covering consent UI, chooser enrichment, callback handling, and related flows.

UI Changes

Screenshot_05-May_17-29-13_4449

when handle mode is picker or picker with random

Screenshot_05-May_17-31-05_26694

when handle mode is random

@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

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

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview, Comment May 5, 2026 7:01pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 4, 2026

🦋 Changeset detected

Latest commit: a7022f3

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

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Propagates and validates epds_handle_mode through auth flows and callbacks, injects an enriched client script that presents email-first identifiers and accessible handle tooltips (hiding handles in random mode), refactors client-id/request_uri resolution, and adds/updates tests and E2E steps to cover consent, chooser, and account enrichment plus login readiness.

Changes

Email-First Account Presentation & Handle-Mode Enrichment

Layer / File(s) Summary
Data / Types
packages/shared/src/crypto.ts, packages/pds-core/src/lib/oauth-request-context.ts
CallbackParams adds optional epds_handle_mode; new types OAuthRequestQuery and ResolveClientIdFromRequestUri exported.
Core Helpers / URL Builders
packages/pds-core/src/lib/epds-callback-authorize.ts, packages/pds-core/src/lib/oauth-request-context.ts
Added resolveCallbackHandleMode, buildEpdsCallbackAuthorizeUrl, and resolveOAuthClientIdFromQuery to validate/construct authorize/callback redirects and centralize client-id resolution.
Server Routing / Payloads
packages/auth-service/src/routes/complete.ts, packages/auth-service/src/routes/choose-handle.ts, packages/pds-core/src/index.ts
Auth routes now include epds_handle_mode in signed callback/query payloads; resolveCompleteIdentity helper added; /oauth/epds-callback uses buildEpdsCallbackAuthorizeUrl and receives refactored resolveClientIdFromRequestUri.
Middleware / CSS Injection
packages/pds-core/src/lib/client-css-injection.ts, packages/pds-core/src/chooser-enrichment.ts
Client CSS injection and chooser enrichment now use centralized client-id resolution (resolveOAuthClientIdFromQuery) with updated error logging and typed resolver dependency.
Client Enrichment Script
packages/pds-core/src/chooser-enrichment.ts
Major refactor: captures __sessions/__deviceSessions, matches accounts by trimmed own-text, splits page-specific enrichment (consent, chooser, account pages), implements random-mode behavior by hiding handles and appending .epds-hidden-handle-description elements wired via aria-describedby, adds identity tooltip/icon, and exposes resolveClientIdFromRequestUri dependency.
Preview/Consent Templates
packages/pds-core/src/lib/preview-consent.ts
Consent preview injects <meta name="epds-handle-mode"> and inline chooser-enrichment script; preview fixture includes handleMode.
Login Page Readiness
packages/auth-service/src/routes/login-page.ts
Email OTP form initially rendered disabled with data-epds-login-ready="false"; client script attaches handler then flips readiness and enables the submit button.
E2E Steps / Features
features/*.feature, e2e/step-definitions/consent.steps.ts, e2e/step-definitions/session-reuse-bugs.steps.ts
Added consent scenarios and step defs to assert email-first display, identity tooltip contents, and accessible hidden-handle descriptions; updated chooser accessibility assertions to require aria-describedby.
Tests
packages/*/__tests__/*
Added/extended tests: chooser-enrichment harness and interaction tests, epds-callback authorize builder tests, callback-handle-mode threading tests, preview chooser/consent tests, client-css injection regressions, login-page readiness tests, and crypto tests to include epds_handle_mode in signing/verification.
Docs / Changelog / Issue
.changeset/sign-in-account-presentation.md, CHANGELOG.md, .beads/issues.jsonl, package.json
Added changeset and changelog entry for 0.6.2, created epic issue entry, and bumped package version to 0.6.2.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant AuthService as Auth Service
    participant DB as Database
    participant PDS as PDS Core / OAuth Provider
    participant DOM as Browser DOM (Enrichment Script)
    participant User

    Browser->>AuthService: POST /auth/complete (with flow cookie)
    AuthService->>DB: get auth_flow (handle_mode)
    DB-->>AuthService: return handle_mode
    AuthService->>AuthService: resolve identity (email / DID)
    AuthService->>PDS: redirect → /oauth/epds-callback?epds_handle_mode=...
    PDS->>PDS: buildEpdsCallbackAuthorizeUrl(..., handleMode)
    PDS-->>Browser: render /oauth/authorize HTML (meta + enrichment script)
    Browser->>DOM: execute enrichment script
    DOM->>DOM: read meta handle-mode, normalize accounts, enrich UI
    alt handleMode == random
        DOM->>DOM: hide handles, add aria-describedby + tooltip
    else
        DOM->>DOM: show handle and email together
    end
    User->>DOM: hover/focus → tooltip reveals handle/explanation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • aspiers
  • s-adamantine

🐰 Hops in with delight
Email shines bright where handles once hid,
A tooltip reveals what the mode truly bids,
Random-mode handles now safely tucked away,
Aria-described for the reader's relay! 📋✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR comprehensively addresses all #143 coding requirements: generalizes enrichment walker to cover chooser/consent/account pages, hides random-mode handles via display:none/aria-hidden, adds keyboard/screen-reader accessible tooltip with aria-describedby, plumbs HandleMode through injected script, and includes extensive regression tests for all scenarios.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with #143 objectives. Version bump (0.6.1→0.6.2), login-page readiness gate, and changeset documentation are supporting infrastructure changes that align with the PR's scope of sign-in flow improvements.
Title check ✅ Passed The title clearly describes the main change: restoring account enrichment with tooltip explanations for OAuth flows. It directly relates to the PR's primary objective of adding email-first presentation and tooltip behavior for random-handle modes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 143-oauth-enrichment-cover-all-pages-hide-handles-in-random-mode-explain-via-tooltip

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 and usage tips.

@railway-app
Copy link
Copy Markdown

railway-app Bot commented May 4, 2026

🚅 Deployed to the pr-5c336d-148 environment in ePDS

Service Status Web Updated (UTC)
@certified-app/auth-service ✅ Success (View Logs) Web May 5, 2026 at 7:55 pm
@certified-app/pds-core ✅ Success (View Logs) Web May 5, 2026 at 7:02 pm
@certified-app/demo ✅ Success (View Logs) Web May 5, 2026 at 3:35 pm
@certified-app/demo untrusted ✅ Success (View Logs) Web May 5, 2026 at 3:34 pm

@blacksmith-sh

This comment has been minimized.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/auth-service/src/routes/complete.ts (1)

164-169: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Sign epds_handle_mode as part of the callback contract.

epds_handle_mode is being appended after signCallback(), so the browser can alter it before /oauth/epds-callback reaches pds-core. Because the callback value is later used to drive the chooser/consent presentation, this makes the privacy/display mode user-tamperable. Please either include it in the signed payload end-to-end or stop trusting the callback query for this field.

As per coding guidelines, packages/{auth-service,pds-core}/**/*.{ts,tsx,js,mjs}: All epds-callback redirects must be HMAC-SHA256 signed using signCallback()/verifyCallback() from @certified-app/shared.

Also applies to: 208-213

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/auth-service/src/routes/complete.ts` around lines 164 - 169, The
fix: include epds_handle_mode in the signed callback instead of appending it
afterward — add epds_handle_mode: flow.handleMode into the callbackParams object
before calling signCallback(callbackParams, ctx.config.epdsCallbackSecret) so
the generated ts/sig cover that field, and keep using URLSearchParams(params)
afterwards; ensure any other occurrences (the similar block around the later
208-213) follow the same pattern so all epds-callback redirects are HMAC-SHA256
signed via signCallback()/verifyCallback().
packages/auth-service/src/routes/choose-handle.ts (1)

366-371: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid adding epds_handle_mode outside the signed callback payload.

This POST path has the same integrity gap as /auth/complete: epds_handle_mode is mutated onto the redirect URL after signCallback(). That leaves the mode browser-controlled on the hop to pds-core, so the final consent/chooser UI can be flipped without invalidating the callback signature.

As per coding guidelines, packages/{auth-service,pds-core}/**/*.{ts,tsx,js,mjs}: All epds-callback redirects must be HMAC-SHA256 signed using signCallback()/verifyCallback() from @certified-app/shared.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/auth-service/src/routes/choose-handle.ts` around lines 366 - 371,
The code currently appends epds_handle_mode to params after calling
signCallback, leaving it unsigned; instead, if flow.handleMode is present add it
into the callbackParams object before calling signCallback (e.g. set
callbackParams.epds_handle_mode = flow.handleMode), then call
signCallback(callbackParams, ctx.config.epdsCallbackSecret) and build
URLSearchParams from the resulting signed callback (remove the later
params.set('epds_handle_mode', ...) call); this ensures the handle mode is
included in the HMAC produced by signCallback/verifyCallback.
🧹 Nitpick comments (2)
.changeset/preserve-handle-mode-callback.md (1)

5-5: ⚡ Quick win

Rewrite the summary line in plainer end-user language.

app-requested handle display mode reads like implementation terminology. Since this first line is the headline release note for End users, I’d rephrase it in terms of what people actually see during sign-up/approval.

As per coding guidelines, .changeset/*.md: Changeset summary line is the first non-frontmatter line, read by every listed audience; if End users is an audience, write in plain language without OTP/DID/PAR/OAuth jargon or implementation concepts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/preserve-handle-mode-callback.md at line 5, Rewrite the first
non-frontmatter line (currently: "Sign-up screens now keep the app-requested
handle display mode through the final approval step.") into plain end-user
language that describes what the user sees during sign-up/approval without
implementation jargon; e.g. state that the app will remember and show the
preferred handle/display option throughout the sign-up and final approval
screens, using simple terms like "remember" or "keep showing" instead of
"app-requested handle display mode" or other technical phrases.
e2e/step-definitions/session-reuse-bugs.steps.ts (1)

439-506: ⚡ Quick win

Assert the exact hidden handle, not just a non-empty suffix.

This still passes if aria-describedby points at a generic placeholder or the wrong row's description. Since the callback already has handleLabel, capture its text and compare the description body to that exact hidden handle.

Suggested tightening
 type HiddenHandleDescriptionRow = {
   describedBy: string | null
   descriptions: {
     id: string
     isHiddenHandleDescription: boolean
     text: string
   }[]
   emailTitle: string | null
+  hiddenHandleText: string
   rowIndex: number
 }
 ...
             return {
               describedBy,
               descriptions,
               emailTitle: emailLabel.getAttribute('title'),
+              hiddenHandleText: handleLabel.textContent?.trim() ?? '',
               rowIndex,
             }
 ...
-      expect(
-        descriptionText.slice(prefixIndex + prefix.length).trim().length,
-      ).toBeGreaterThan(0)
+      expect(descriptionText.slice(prefixIndex + prefix.length).trim()).toBe(
+        row.hiddenHandleText,
+      )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/step-definitions/session-reuse-bugs.steps.ts` around lines 439 - 506, The
test currently only asserts that the hidden-handle description contains a
non-empty suffix; instead capture the actual handle text from the DOM (use
handleLabel.textContent.trim() inside the evaluateAll mapping and include it on
the returned HiddenHandleDescriptionRow), then in the outer assertions compare
description.text exactly (or ensure it contains) that captured handleText (not
just non-empty trimmed suffix). Update references in the mapping for handleLabel
and the returned object (e.g., add handleText) and replace the loose suffix
checks on descriptionText with a precise comparison against that handleText.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/step-definitions/consent.steps.ts`:
- Around line 162-172: The test reads aria-describedby into describedBy and
constructs a locator with page.locator(`#${describedBy?.trim()}`), but
aria-describedby can contain multiple space-separated IDs; split describedBy on
whitespace (or use describedBy?.split(/\s+/)[0]) and guard for null/empty before
building the selector so you only target the first ID; update the code around
describedBy, tooltipControl and the tooltip locator to use the first token (and
bail or assert if none) when calling page.locator and subsequent expects.

In `@packages/pds-core/src/chooser-enrichment.ts`:
- Around line 895-910: The catch block that silences failures when resolving
request_uri/metadata should log the failure at debug level: thread the pds-core
logger into this middleware (or import the existing logger) and in the catch for
the resolveOAuthClientIdFromQuery/resolveMeta sequence emit logger.debug with a
concise message that includes the failing inputs (e.g. the query/request_uri and
resolved clientId if available) and the caught error/stack; keep behavior
unchanged otherwise so metaMode still falls back to query/env. Ensure the log
uses logger.debug(...) and references resolveOAuthClientIdFromQuery,
resolveClientIdFromRequestUri and resolveMeta to locate the code to modify.

---

Outside diff comments:
In `@packages/auth-service/src/routes/choose-handle.ts`:
- Around line 366-371: The code currently appends epds_handle_mode to params
after calling signCallback, leaving it unsigned; instead, if flow.handleMode is
present add it into the callbackParams object before calling signCallback (e.g.
set callbackParams.epds_handle_mode = flow.handleMode), then call
signCallback(callbackParams, ctx.config.epdsCallbackSecret) and build
URLSearchParams from the resulting signed callback (remove the later
params.set('epds_handle_mode', ...) call); this ensures the handle mode is
included in the HMAC produced by signCallback/verifyCallback.

In `@packages/auth-service/src/routes/complete.ts`:
- Around line 164-169: The fix: include epds_handle_mode in the signed callback
instead of appending it afterward — add epds_handle_mode: flow.handleMode into
the callbackParams object before calling signCallback(callbackParams,
ctx.config.epdsCallbackSecret) so the generated ts/sig cover that field, and
keep using URLSearchParams(params) afterwards; ensure any other occurrences (the
similar block around the later 208-213) follow the same pattern so all
epds-callback redirects are HMAC-SHA256 signed via
signCallback()/verifyCallback().

---

Nitpick comments:
In @.changeset/preserve-handle-mode-callback.md:
- Line 5: Rewrite the first non-frontmatter line (currently: "Sign-up screens
now keep the app-requested handle display mode through the final approval
step.") into plain end-user language that describes what the user sees during
sign-up/approval without implementation jargon; e.g. state that the app will
remember and show the preferred handle/display option throughout the sign-up and
final approval screens, using simple terms like "remember" or "keep showing"
instead of "app-requested handle display mode" or other technical phrases.

In `@e2e/step-definitions/session-reuse-bugs.steps.ts`:
- Around line 439-506: The test currently only asserts that the hidden-handle
description contains a non-empty suffix; instead capture the actual handle text
from the DOM (use handleLabel.textContent.trim() inside the evaluateAll mapping
and include it on the returned HiddenHandleDescriptionRow), then in the outer
assertions compare description.text exactly (or ensure it contains) that
captured handleText (not just non-empty trimmed suffix). Update references in
the mapping for handleLabel and the returned object (e.g., add handleText) and
replace the loose suffix checks on descriptionText with a precise comparison
against that handleText.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3cebbd6d-6d03-4125-9514-8c20d849467e

📥 Commits

Reviewing files that changed from the base of the PR and between 2acbc43 and 8d66b71.

📒 Files selected for processing (21)
  • .beads/issues.jsonl
  • .changeset/preserve-handle-mode-callback.md
  • .changeset/random-handle-consent-metadata.md
  • .changeset/scoped-account-enrichment.md
  • e2e/step-definitions/consent.steps.ts
  • e2e/step-definitions/session-reuse-bugs.steps.ts
  • features/consent-screen.feature
  • features/session-reuse-bugs.feature
  • 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__/chooser-enrichment.test.ts
  • packages/pds-core/src/__tests__/epds-callback-authorize.test.ts
  • packages/pds-core/src/__tests__/preview-chooser.test.ts
  • packages/pds-core/src/__tests__/preview-consent.test.ts
  • packages/pds-core/src/chooser-enrichment.ts
  • packages/pds-core/src/index.ts
  • packages/pds-core/src/lib/client-css-injection.ts
  • packages/pds-core/src/lib/epds-callback-authorize.ts
  • packages/pds-core/src/lib/oauth-request-context.ts
  • packages/pds-core/src/lib/preview-consent.ts

Comment thread e2e/step-definitions/consent.steps.ts Outdated
Comment thread packages/pds-core/src/chooser-enrichment.ts
@Kzoeps
Copy link
Copy Markdown
Contributor Author

Kzoeps commented May 5, 2026

(reply generated by gpt-5.5)

Review follow-up status:

  • Fixed in 49c64e1: aria-describedby token handling, chooser-enrichment debug logging, Sonar mechanical code-smell items, test HTTP hotspot annotation, complete-route complexity refactor, session-reuse hidden-handle assertion tightening, and chooser-enrichment test duplication refactor.
  • Addressed earlier in 60aa045: consolidated the generated-handle changeset wording.
  • Leaving the epds_handle_mode HMAC suggestions as-is by maintainer decision: this field is UI/display-only and is not used as an authorization/security decision.
  • Blacksmith E2E failures are intentionally left for separate follow-up per maintainer.

@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-148 May 5, 2026 06:44 Destroyed
@Kzoeps
Copy link
Copy Markdown
Contributor Author

Kzoeps commented May 5, 2026

(reply generated by gpt-5.5)

Follow-up: added 02b4c01 to further reduce the remaining chooser-enrichment test duplication reported by Sonar after the first fix. Local validation for that follow-up passed: pnpm format:check, pnpm lint, pnpm typecheck, and pnpm vitest run packages/pds-core/src/__tests__/chooser-enrichment.test.ts.

@Kzoeps
Copy link
Copy Markdown
Contributor Author

Kzoeps commented May 5, 2026

@coderabbitai please review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

The first PR description image (17:29:13) shows an OAuth authorization consent screen for certified-appdemo-untrusted-epds-pr-148.up.railway.app. The primary account identifier shown is the AT Protocol handle 12fw07.certified-apppds-core-epds-pr-148.up.railway.app, with a tooltip reading "This handle is associated with karma+2930190@gainforest.net." — the handle is the primary display, email surfaced via tooltip.

The second PR description image (17:31:05) shows a similar OAuth consent screen in random-handle mode. The primary account identifier is now the email karmadsf@jlksad.com (email-first), and the tooltip reads "Public AT Protocol handle: @r9m38d.certified-apppds-core-epds-pr-148.up.railway.app. Handles are public account names used by AT Protocol apps." — demonstrating the email-first + accessible-handle-via-tooltip behavior for random-handle mode.


@Kzoeps Sure, triggering a review now!

✅ Actions performed

Review triggered.

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.

@Kzoeps
Copy link
Copy Markdown
Contributor Author

Kzoeps commented May 5, 2026

@coderabbitai please review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

The first PR description image (17:29:13) shows an OAuth authorization consent screen where the AT Protocol handle 12fw7o.certified-apppds-core-epds-pr-148.up.railway.app is the primary account identifier, with a tooltip surfacing the associated email: "This handle is associated with karma+2930190@gainforest.net."

The second PR description image (17:31:05) shows the same consent screen in random-handle mode — the primary identifier is now the email karmadsf@jlksad.com (email-first), with a tooltip exposing the underlying AT Protocol handle: "Public AT Protocol handle: @r9m38d.certified-apppds-core-epds-pr-148.up.railway.app. Handles are public account names used by AT Protocol apps."


@Kzoeps Sure, triggering a review now!

✅ Actions performed

Review triggered.

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.

Comment on lines +1167 to +1168
sendOtpForm.dataset.epdsLoginReady = 'true';
sendOtpBtn.disabled = false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Kzoeps These seem to trigger unconditionally on page load, which would make this disable/enable mechanism ineffective.

Comment thread .changeset/clean-exit-on-expired-signin.md Outdated
: undefined
}

export function buildEpdsCallbackAuthorizeUrl(opts: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems a little odd. Surely the code already has had to construct callback URLs for a long time, so why is a new function needed now?

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shared/src/crypto.ts (1)

28-34: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compare UTF-8 byte lengths before calling crypto.timingSafeEqual.

The timingSafeEqual() helper guards on JavaScript string .length, which counts UTF-16 code units, not UTF-8 bytes. When passed non-ASCII strings with the same .length but different byte lengths (e.g., '£' vs 'a' both .length === 1), Buffer.from(a) and Buffer.from(b) produce buffers of different byte lengths, causing crypto.timingSafeEqual() to throw RangeError instead of returning false. This is a timing side-channel: an attacker observes exception vs. return to infer whether inputs have multibyte characters.

Suggested fix
 export function timingSafeEqual(a: string, b: string): boolean {
-  if (a.length !== b.length) {
-    const dummy = Buffer.alloc(a.length)
+  const aBytes = Buffer.byteLength(a, 'utf8')
+  const bBytes = Buffer.byteLength(b, 'utf8')
+  if (aBytes !== bBytes) {
+    const dummy = Buffer.alloc(Math.max(aBytes, bBytes))
     crypto.timingSafeEqual(dummy, dummy)
     return false
   }
-  return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))
+  return crypto.timingSafeEqual(
+    Buffer.from(a, 'utf8'),
+    Buffer.from(b, 'utf8'),
+  )
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/crypto.ts` around lines 28 - 34, The timingSafeEqual
function currently compares JavaScript string .length (UTF-16 code units) which
can differ from UTF-8 byte length and can cause
crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)) to throw; update
timingSafeEqual to compare UTF-8 byte lengths using Buffer.byteLength(a, 'utf8')
and Buffer.byteLength(b, 'utf8'), and if the byte lengths differ call
crypto.timingSafeEqual on a dummy buffer of the larger byte length (e.g.,
Buffer.alloc(maxLen)) to preserve constant-time behavior and then return false;
only convert to Buffer.from(..., 'utf8') and call crypto.timingSafeEqual when
the UTF-8 byte lengths are equal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/auth-service/src/routes/login-page.ts`:
- Line 672: The <noscript> warning is currently nested inside the `#step-email`
container (which gets the "hidden" class when initialStep === 'otp') so no-JS
users landing on the OTP step won't see it; move the <noscript><div
class="flash-msg error">…</div></noscript> element out of the `#step-email` block
so it is rendered alongside the page-level content (outside the email step
wrapper) to ensure it is always visible regardless of initialStep/hidden state,
keeping the same markup and classes so styling remains unchanged.

---

Outside diff comments:
In `@packages/shared/src/crypto.ts`:
- Around line 28-34: The timingSafeEqual function currently compares JavaScript
string .length (UTF-16 code units) which can differ from UTF-8 byte length and
can cause crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)) to throw;
update timingSafeEqual to compare UTF-8 byte lengths using Buffer.byteLength(a,
'utf8') and Buffer.byteLength(b, 'utf8'), and if the byte lengths differ call
crypto.timingSafeEqual on a dummy buffer of the larger byte length (e.g.,
Buffer.alloc(maxLen)) to preserve constant-time behavior and then return false;
only convert to Buffer.from(..., 'utf8') and call crypto.timingSafeEqual when
the UTF-8 byte lengths are equal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00bc0a21-910e-4e27-b253-28ddd84d98f7

📥 Commits

Reviewing files that changed from the base of the PR and between c755fe9 and 263bbbf.

📒 Files selected for processing (16)
  • .changeset/sign-in-account-presentation.md
  • CHANGELOG.md
  • e2e/step-definitions/consent.steps.ts
  • features/consent-screen.feature
  • package.json
  • packages/auth-service/src/__tests__/build-epds-callback-url.test.ts
  • packages/auth-service/src/__tests__/callback-handle-mode.test.ts
  • packages/auth-service/src/__tests__/login-page.test.ts
  • packages/auth-service/src/routes/choose-handle.ts
  • packages/auth-service/src/routes/complete.ts
  • packages/auth-service/src/routes/login-page.ts
  • packages/pds-core/src/__tests__/client-css-injection.test.ts
  • packages/pds-core/src/index.ts
  • packages/pds-core/src/lib/client-css-injection.ts
  • packages/shared/src/__tests__/crypto.test.ts
  • packages/shared/src/crypto.ts
✅ Files skipped from review due to trivial changes (4)
  • package.json
  • CHANGELOG.md
  • .changeset/sign-in-account-presentation.md
  • features/consent-screen.feature
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/pds-core/src/lib/client-css-injection.ts
  • packages/auth-service/src/tests/callback-handle-mode.test.ts
  • packages/auth-service/src/tests/login-page.test.ts
  • packages/auth-service/src/routes/complete.ts
  • packages/pds-core/src/index.ts
  • e2e/step-definitions/consent.steps.ts

Comment thread packages/auth-service/src/routes/login-page.ts Outdated
Comment thread CHANGELOG.md
@railway-app railway-app Bot temporarily deployed to ePDS / pr-5c336d-148 May 5, 2026 18:33 Destroyed
@railway-app railway-app Bot temporarily deployed to ePDS / pr-5c336d-148 May 5, 2026 18:47 Destroyed
@railway-app railway-app Bot temporarily deployed to ePDS / pr-5c336d-148 May 5, 2026 18:52 Destroyed
@Kzoeps Kzoeps force-pushed the 143-oauth-enrichment-cover-all-pages-hide-handles-in-random-mode-explain-via-tooltip branch from d77caed to 60d4ed4 Compare May 5, 2026 18:54
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 5, 2026

@blacksmith-sh

This comment has been minimized.

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.

OAuth enrichment: cover all pages, hide handles in random mode, explain via tooltip

2 participants