Skip to content

feat(server): informed consent for agent user_claimed flow#1081

Merged
buremba merged 2 commits into
mainfrom
feat/claim-informed-consent
May 26, 2026
Merged

feat(server): informed consent for agent user_claimed flow#1081
buremba merged 2 commits into
mainfrom
feat/claim-informed-consent

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 26, 2026

What

Closes the informed-consent gaps on the agent user_claimed flow (the severe auto-approve was already fixed by the deployed consent-copy build; confirmed e2e on prod — magic-link arrival leaves the token authorization_pending until an explicit Approve).

  • GET /oauth/device/info?user_code=… (auth-gated): resolves a pending device code to its requesting client name + scopes, so the consent page can show who is asking and what they get.
  • The magic-link email reframes as an authorization request (subject + heading + CTA) when its callbackURL targets the device consent page — so a user doesn't grant third-party access thinking they're completing a routine sign-in. Normal login emails are unchanged (default mode: 'sign-in').

Paired owletto change (lobu-ai/owletto#230): the consent page fetches /oauth/device/info and renders "Acme Agent is requesting access — Access: mcp:read, mcp:write."

Why

pi flagged that approving a blind code, with a "Sign in to Lobu" email, is a phishing-shaped consent for a flow whose real intent is "authorize an agent to act as you." This makes the email and the consent screen say what's actually happening, so approval is informed.

Test evidence

agent-claim-discovery.test.ts now 7/7 green vs real PG17+pgvector under Node 22 — adds: info endpoint returns client_name + scopes (authed), 401 unauthenticated, 400 unknown user_code — alongside the existing discovery + full user_claimed loop.

Notes

No scope clamp — once consent is explicit and informed, the user grants the full requested scope (read+write), which is correct. Deferred: per-email rate limit + token provenance (hardening), and the auto-approve root-cause was the old SPA's conditional auto-submit, removed in the deployed build.

Summary by CodeRabbit

  • New Features

    • OAuth device info endpoint: returns requesting app name and requested permissions during device authorization.
    • Magic-link emails: show distinct authorization messaging when a sign-in is requesting app permissions.
  • Tests

    • Added integration tests covering authenticated and unauthenticated device info requests.
  • Chores

    • Updated an internal subproject reference.

Review Change Stack

Two informed-consent additions for the agent email-claim flow:
- GET /oauth/device/info?user_code=... (auth-gated) returns the requesting
  client name + scopes for a pending device code, so the consent page can show
  WHO is asking and WHAT they get instead of approving a blind code.
- The magic-link email reframes as an authorization request (subject + body)
  when its callbackURL targets the device consent page — so a user doesn't grant
  third-party access thinking they're completing a routine sign-in. Normal
  login emails are unchanged.

The consent gate itself is already explicit (no auto-approve in the current
build, confirmed e2e on prod). Tests cover the info endpoint (authed/401/400)
alongside the existing discovery + full-loop suite.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7de818ba-9870-4882-95a1-e673567eb5ff

📥 Commits

Reviewing files that changed from the base of the PR and between 6c58e24 and f6e3ac2.

📒 Files selected for processing (1)
  • packages/owletto

📝 Walkthrough

Walkthrough

Adds device consent support: magic-link emails gain an authorization mode and subject, auth flow detects device callbacks to choose that mode, a new authenticated GET /oauth/device/info endpoint returns client and scope info for a device code, and tests cover the endpoint behavior.

Changes

Device OAuth Authorization Consent Flow

Layer / File(s) Summary
Magic-link email template with authorization variant
packages/server/src/email/templates/magic-link.tsx
MagicLinkEmail accepts optional mode (`'sign-in'
Auth flow device authorization detection and email routing
packages/server/src/auth/index.tsx
Auth magic-link sender parses callbackURL to detect /oauth/device; when detected uses authorizeAppSubject and passes mode: 'authorize' to MagicLinkEmail, else defaults to sign-in subject/mode.
Device authorization info endpoint
packages/server/src/auth/oauth/routes.ts
New authenticated GET /oauth/device/info accepts user_code, validates and normalizes it, fetches device code and its client, and returns client_name, client_id, parsed scopes, and resource; returns 400 or OAuth errors for invalid/missing/not-found.
Device endpoint test coverage
packages/server/src/__tests__/integration/oauth/agent-claim-discovery.test.ts
Adds tests for GET /oauth/device/info: authenticated response includes client name and scopes, unauthenticated returns 401, unknown user_code returns 400; imports createTestDeviceCode and createTestOAuthClient fixtures.
Subproject gitlink update
packages/owletto
Updates pinned commit for packages/owletto subproject gitlink.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • lobu-ai/lobu#1071: Sets magic-link callbackURL to /oauth/device/...; related to the device consent email and endpoint implemented here.

Poem

🐰 I nibble at scopes in moonlit code,
A magic link hops down the road,
"Authorize" I whisper soft and spry,
Consent appears beneath the sky,
Tiny rabbit cheers — hop, approve, and go!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides comprehensive context (What/Why/Test evidence/Notes sections) but lacks the structured test-plan checklist from the repository template. Add the standard test-plan checklist section (bun run check:fix, typecheck, package validation) to clearly document which validations were performed before merge.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: adding informed consent (via a new device info endpoint and reframed authorization email) to the agent user_claimed OAuth flow.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/claim-informed-consent

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

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 26, 2026

bug_free 88, simplicity 86, slop 0, bugs 0, 0 blockers

Read diff/logs: typecheck, unit, and integration all exit 0; integration includes /oauth/device/info coverage. Skipped extra server boot; inspected Better Auth magic-link URL construction instead. Working tree has a dirty packages/owletto submodule pointer outside the HEAD diff.

Full verdict JSON
{
  "bug_free_confidence": 88,
  "bugs": 0,
  "slop": 0,
  "simplicity": 86,
  "blockers": [],
  "change_type": "feat",
  "behavior_change_risk": "low",
  "tests_adequate": true,
  "suggested_fixes": [],
  "notes": "Read diff/logs: typecheck, unit, and integration all exit 0; integration includes /oauth/device/info coverage. Skipped extra server boot; inspected Better Auth magic-link URL construction instead. Working tree has a dirty packages/owletto submodule pointer outside the HEAD diff.",
  "categories": {
    "src": 68,
    "tests": 44,
    "docs": 0,
    "config": 0,
    "deps": 0,
    "migrations": 0,
    "ci": 0,
    "generated": 0
  }
}

Local review gate — branch protection can require the pi-review commit status. See docs/REVIEW_SCHEMA.md.

…ing drift)

Advances packages/owletto to owletto/main (79932d7): brings the device-consent
'who + scopes' change (owletto#230) and clears the pre-existing pointer drift
from owletto#226/#227/#228 (deploy/monitoring commits, no frontend impact) that
check-drift was flagging on every lobu PR.
@buremba buremba merged commit 48cd6ea into main May 26, 2026
20 checks passed
@buremba buremba deleted the feat/claim-informed-consent branch May 26, 2026 16:12
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