Skip to content

fix: use upstream OAuth consent UI instead of broken auth-service reimplementation#21

Open
aspiers wants to merge 2 commits intomainfrom
fix/consent-use-upstream-oauth-ui
Open

fix: use upstream OAuth consent UI instead of broken auth-service reimplementation#21
aspiers wants to merge 2 commits intomainfrom
fix/consent-use-upstream-oauth-ui

Conversation

@aspiers
Copy link
Contributor

@aspiers aspiers commented Mar 14, 2026

Summary

  • Replace the auth-service consent page (which had hard-coded permissions ignoring actual OAuth scopes) with the stock @atproto/oauth-provider consent UI (consent-view.tsx)
  • epds-callback now redirects through /oauth/authorize after account creation, letting the upstream middleware handle consent with actual scopes/permissionSets
  • Remove consent.ts, consent.test.ts, hasClientLogin/recordClientLogin, and the client_logins table (v8 migration)
  • Simplify complete.ts by removing consent branching

Problem

The auth-service consent page displayed "Read and write posts", "Access your profile", "Manage your follows" regardless of what OAuth scopes the client actually requested. It also used its own client_login table instead of the atproto provider's built-in authorizedClients tracking.

How it works now

After OTP verification and account creation (if new user), epds-callback calls upsertDeviceAccount() to bind the device session, then redirects to the stock /oauth/authorize endpoint. The upstream oauthMiddleware:

  1. Finds the device session via the browser's dev-id cookie
  2. Calls provider.authorize() which checks checkConsentRequired() against actual scopes
  3. Auto-approves if no consent needed, or renders the upstream consent UI with real permissionSets

Design doc

See docs/design/consent-flow-fix.md for the full analysis, implementation plan, and research findings about the middleware's device session handling and auto-approve conditions.

Net change

-411 lines (213 added, 624 removed)

Summary by CodeRabbit

  • Bug Fixes

    • Delegated consent to the upstream OAuth UI so requested scopes display correctly and consent tracking is scope-aware
    • Simplified authentication paths for existing accounts, streamlining redirects and reducing redundant screens
  • Documentation

    • Added a design doc describing the new consent flow and implementation plan
  • Tests

    • Updated/added tests around migrations and handle validation; removed legacy consent route tests
  • Chores

    • Migrated consent tracking away from the local DB and ratcheted test-coverage thresholds

…mplementation

The auth-service consent page had hard-coded permissions ('Read and write
posts', 'Access your profile', 'Manage your follows') that ignored the
actual OAuth scopes requested by the client. This replaces it with the
stock @atproto/oauth-provider consent UI.

Changes:
- epds-callback now redirects through /oauth/authorize after account
  creation and device session binding, letting the stock oauthMiddleware
  handle consent with actual scopes via consent-view.tsx
- Remove auth-service consent page (consent.ts) and its tests
- Remove client_login table, hasClientLogin/recordClientLogin from db.ts
- Add v8 migration to drop client_logins table
- Simplify complete.ts: remove consent branching (step 5b)
- Add design doc (docs/design/consent-flow-fix.md) with research findings
@vercel
Copy link

vercel bot commented Mar 14, 2026

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

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview, Comment Mar 14, 2026 3:27pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

This PR removes custom consent handling from the auth service, deletes the consent route and its tests, simplifies /auth/complete to delegate consent to upstream OAuth middleware, changes pds-core epds-callback to redirect through the stock /oauth/authorize flow, and adds a DB migration (v8) dropping client_logins. It also adds design documentation and updates coverage/handle tests.

Changes

Cohort / File(s) Summary
Design Documentation
docs/design/consent-flow-fix.md
Add design doc describing root issues, target flow delegating consent to upstream OAuth middleware, and implementation plan.
Consent Route & Tests Removed
packages/auth-service/src/routes/consent.ts, packages/auth-service/src/__tests__/consent.test.ts
Delete consent router module and its full test suite (GET/POST handlers, HTML rendering, client-login tests, signature/flow tests).
Auth Service Integration
packages/auth-service/src/index.ts, packages/auth-service/src/routes/complete.ts
Remove mounting of consent router; simplify /auth/complete to treat existing users as a single path and redirect via pds-core epds-callback/stock OAuth flow (removed explicit consent logic and per-client login handling).
PDS Core Callback Rework
packages/pds-core/src/index.ts
Change epds-callback to call requestManager.get(requestUri) without deviceId/client fetch, upsert device-account, then redirect to stock /oauth/authorize (delegate consent handling to OAuth middleware) instead of issuing auth codes directly.
Database Migration & DB API
packages/shared/src/db.ts, packages/shared/src/__tests__/db.test.ts
Add migration v8 to drop client_logins table; remove hasClientLogin() and recordClientLogin() methods; add tests verifying v8 migration and table removal.
New/Updated Tests
packages/shared/src/__tests__/handle.test.ts
Add tests for validateLocalPart and LOCAL_PART_MIN/MAX behavior and constraints.
Build/Test Config
vitest.config.ts, AGENTS.md
Increase coverage ratchet thresholds (statements, branches, functions) and add Coverage Ratcheting Policy to AGENTS.md.

Sequence Diagram

sequenceDiagram
    actor User
    participant AuthService as auth-service
    participant PDSCore as pds-core
    participant OAuthMW as stock OAuth middleware
    participant OAuthProvider as OAuth Provider

    User->>AuthService: Initiate login / authenticate
    AuthService->>PDSCore: Redirect to epds-callback with PAR request_uri
    PDSCore->>OAuthProvider: requestManager.get(requestUri)
    OAuthProvider-->>PDSCore: Return clientId & scope info
    PDSCore->>PDSCore: Upsert device ↔ account association
    PDSCore->>OAuthMW: Redirect user to /oauth/authorize (with request_uri/state)
    OAuthMW->>OAuthMW: Evaluate prior consent / prompt params
    alt Auto-approve
        OAuthMW->>OAuthProvider: Issue authorization code
        OAuthProvider-->>OAuthMW: Code issued
        OAuthMW-->>User: Redirect to client with code
    else Consent required
        OAuthMW-->>User: Render consent UI
        User->>OAuthMW: Approve scopes
        OAuthMW->>OAuthProvider: Issue authorization code
        OAuthProvider-->>OAuthMW: Code issued
        OAuthMW-->>User: Redirect to client with code
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I nibbled code and found a seam,

Consent now flows up like cream,
Stock OAuth hums the tune,
Redirects neat by afternoon,
Hops of joy — no custom screen!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing the broken auth-service consent implementation with the upstream OAuth consent UI, which is the central architectural shift described across all modified files.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/consent-use-upstream-oauth-ui
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link

@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

🧹 Nitpick comments (1)
packages/pds-core/src/index.ts (1)

276-282: Prefer did for upsertDeviceAccount to reduce nullable coupling.

did is already resolved in successful paths; using account.sub adds avoidable runtime dependency on account shape.

♻️ Suggested hardening
-      await provider.accountManager.upsertDeviceAccount(deviceId, account.sub)
+      if (!did) {
+        res
+          .status(500)
+          .type('html')
+          .send(renderError('Account resolution failed. Please try again.'))
+        return
+      }
+      await provider.accountManager.upsertDeviceAccount(deviceId, did)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/pds-core/src/index.ts` around lines 276 - 282, The call to
provider.accountManager.upsertDeviceAccount currently uses account.sub which
creates an unnecessary runtime dependency on the account shape; change this to
use the already-resolved DID variable (did) instead so the call becomes
provider.accountManager.upsertDeviceAccount(deviceId, did) and remove any
reliance on account.sub in this binding path, ensuring did is defined/validated
before invoking upsertDeviceAccount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/consent-flow-fix.md`:
- Around line 17-19: Update the documentation to use the exact table name
client_logins (plural) for consistency: replace the occurrence of client_login
with client_logins in the sentence that describes the separate consent tracking
and ensure the references to hasClientLogin and recordClientLogin remain correct
and aligned with the schema/migration naming to avoid confusion with the atproto
provider's authorizedClients tracking.
- Around line 49-53: The fenced code blocks containing the flow diagrams (the
OTP verify → ... sequences) are missing a language identifier which triggers
markdownlint MD040; update those backtick fences around the flow diagrams (the
two blocks showing the OTP/auth/consent → ... sequences) to include a language
specifier such as "text" (i.e., change ``` to ```text) so the blocks pass MD040;
ensure both occurrences referenced in the comment (the block around the OTP
verify → /auth/complete ... sequence and the block around the
/oauth/epds-callback → create account ... sequence) are updated.

---

Nitpick comments:
In `@packages/pds-core/src/index.ts`:
- Around line 276-282: The call to provider.accountManager.upsertDeviceAccount
currently uses account.sub which creates an unnecessary runtime dependency on
the account shape; change this to use the already-resolved DID variable (did)
instead so the call becomes
provider.accountManager.upsertDeviceAccount(deviceId, did) and remove any
reliance on account.sub in this binding path, ensuring did is defined/validated
before invoking upsertDeviceAccount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ab703ac-c387-46a9-8e8c-6392f14c5900

📥 Commits

Reviewing files that changed from the base of the PR and between 0f72433 and d2a5bdc.

📒 Files selected for processing (7)
  • docs/design/consent-flow-fix.md
  • packages/auth-service/src/__tests__/consent.test.ts
  • packages/auth-service/src/index.ts
  • packages/auth-service/src/routes/complete.ts
  • packages/auth-service/src/routes/consent.ts
  • packages/pds-core/src/index.ts
  • packages/shared/src/db.ts
💤 Files with no reviewable changes (3)
  • packages/auth-service/src/index.ts
  • packages/auth-service/src/tests/consent.test.ts
  • packages/auth-service/src/routes/consent.ts

Comment on lines +17 to +19
3. **Separate consent tracking** — uses its own `client_login` table
(`hasClientLogin` / `recordClientLogin`) instead of the atproto provider's
built-in `authorizedClients` tracking, which already handles scope-level
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use the exact table name client_logins for consistency.

The doc currently says client_login, but the schema/migration name is client_logins. Please align wording to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/consent-flow-fix.md` around lines 17 - 19, Update the
documentation to use the exact table name client_logins (plural) for
consistency: replace the occurrence of client_login with client_logins in the
sentence that describes the separate consent tracking and ensure the references
to hasClientLogin and recordClientLogin remain correct and aligned with the
schema/migration naming to avoid confusion with the atproto provider's
authorizedClients tracking.

Comment on lines +49 to +53
```
OTP verify → /auth/complete → check consent (auth-service) →
→ show broken consent page (auth-service) →
→ /oauth/epds-callback → setAuthorized() → redirect to client
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Specify a language for fenced code blocks (MD040).

These blocks are missing a language identifier and will keep markdownlint warnings active.

🛠️ Suggested doc fix
-```
+```text
 OTP verify → /auth/complete → check consent (auth-service) →
   → show broken consent page (auth-service) →
   → /oauth/epds-callback → setAuthorized() → redirect to client

- +text
OTP verify → /auth/complete →
→ /oauth/epds-callback → create account if needed, upsertDeviceAccount →
→ redirect to /oauth/authorize?request_uri=... (stock middleware) →
→ stock middleware calls provider.authorize() →
→ if consent needed: renders upstream consent-view.tsx with real scopes →
→ if no consent needed: auto-approves (SSO match) →
→ redirect to client

Also applies to: 57-65

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 49-49: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/consent-flow-fix.md` around lines 49 - 53, The fenced code blocks
containing the flow diagrams (the OTP verify → ... sequences) are missing a
language identifier which triggers markdownlint MD040; update those backtick
fences around the flow diagrams (the two blocks showing the OTP/auth/consent →
... sequences) to include a language specifier such as "text" (i.e., change ```
to ```text) so the blocks pass MD040; ensure both occurrences referenced in the
comment (the block around the OTP verify → /auth/complete ... sequence and the
block around the /oauth/epds-callback → create account ... sequence) are
updated.

- Add handle.test.ts with 13 tests for validateLocalPart()
- Add migration v8 tests verifying client_logins table is dropped
- Ratchet coverage thresholds up (statements: 21, branches: 15,
  functions: 35, lines: 20) to reflect new coverage floor
- Document coverage ratcheting policy in AGENTS.md
Copy link

@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.

🧹 Nitpick comments (1)
packages/shared/src/__tests__/db.test.ts (1)

229-234: Use an exact schema version assertion for this v8 migration test.

At Line 233, >= 8 can silently pass after future migrations, even if this test is not updated. Since the migration runner sets a precise version per applied migration (packages/shared/src/db.ts lines 186-189), this test should assert the exact expected version for this snapshot.

Proposed test hardening
-  it('schema version is at least 8 after migration', () => {
+  it('schema version is exactly 8 after migration', () => {
     const row = db['db']
       .prepare('SELECT version FROM schema_version')
       .get() as { version: number }
-    expect(row.version).toBeGreaterThanOrEqual(8)
+    expect(row.version).toBe(8)
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/__tests__/db.test.ts` around lines 229 - 234, The test
"schema version is at least 8 after migration" currently uses
expect(row.version).toBeGreaterThanOrEqual(8) which can mask future changes;
update the assertion to require the exact schema version produced by the
migration runner by replacing the >= check with expect(row.version).toBe(8)
(i.e., assert the precise value returned by db['db'].prepare('SELECT version
FROM schema_version').get()) so the test fails if the migration runner (the code
that sets the precise version) changes without updating the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/shared/src/__tests__/db.test.ts`:
- Around line 229-234: The test "schema version is at least 8 after migration"
currently uses expect(row.version).toBeGreaterThanOrEqual(8) which can mask
future changes; update the assertion to require the exact schema version
produced by the migration runner by replacing the >= check with
expect(row.version).toBe(8) (i.e., assert the precise value returned by
db['db'].prepare('SELECT version FROM schema_version').get()) so the test fails
if the migration runner (the code that sets the precise version) changes without
updating the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc80c47e-15c6-421a-990e-1caa7b05109c

📥 Commits

Reviewing files that changed from the base of the PR and between d2a5bdc and 9a4c5c3.

📒 Files selected for processing (4)
  • AGENTS.md
  • packages/shared/src/__tests__/db.test.ts
  • packages/shared/src/__tests__/handle.test.ts
  • vitest.config.ts

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.

1 participant