Skip to content

PR-2c-bearer: wire OAuth Bearer middleware into /mcp (the last connection) #217

@IgorShevchik

Description

@IgorShevchik

Context

PR #216 (PR-2c) landed the OAuth install/callback handshake + the B24OAuth factory + dispatcher wiring. After it merges an operator can flip NUXT_BITRIX24_OAUTH_ENABLED=true, complete /api/oauth/install → /api/oauth/callback, and receive a Bearer — but /mcp doesn't accept that Bearer yet. It still authenticates with NUXT_MCP_AUTH_TOKEN. The callback HTML page warns the user about this explicitly ("⚠ Not active yet"), but the wire-up is the load-bearing missing piece of the whole OAuth stack.

This issue tracks that final connection. Split out of #216 to keep that PR's review focused (the middleware touches defineMcpHandler, a different architectural plane).

Scope

  1. MCP Bearer middleware. Extend the MCP handler (server/mcp/index.ts / wherever defineMcpHandler is registered) with a middleware hook that:

    • extracts the Authorization: Bearer <token> header,
    • hashes it (sha256-<hex>) and looks it up via findByBearerHash,
    • on hit, wraps the request in runWithTenant({ memberId, userId, requestId }, () => next()) (the requestId is a fresh 16-byte hex generated here — this is the getRequestId() consumer PR-2c: requestId — populate unconditionally + getRequestId() throwing helper #214 added),
    • on miss / revoked / orphan, returns 401 with the §11 mcp.auth.deny.* event + errorCode.
  2. Flag-gated. When NUXT_BITRIX24_OAUTH_ENABLED=false, the middleware is a no-op and /mcp keeps using NUXT_MCP_AUTH_TOKEN exactly as today (webhook-only forks unchanged).

  3. §11 events (already in the taxonomy, marked "deferred" — flip them to live):

    • mcp.auth.deny.bearer-unknown
    • mcp.auth.deny.bearer-revoked
    • mcp.auth.deny.bearer-orphan
  4. getRequestId() becomes reachable — this is the first production caller. Add the test that proves getRequestId() returns a 32-char hex inside a real middleware-wrapped request.

Acceptance

  • /mcp with a valid minted Bearer (flag on) → tenant context populated, tool call resolves through useBitrix24OAuth.
  • /mcp with an unknown / revoked Bearer → 401 + the matching mcp.auth.deny.* event.
  • /mcp with NUXT_BITRIX24_OAUTH_ENABLED=false → unchanged (NUXT_MCP_AUTH_TOKEN path).
  • requestId populated unconditionally; getRequestId() returns it; the throw path is unreachable in production.
  • Update OAUTH-DESIGN.md §11: flip the mcp.auth.deny.* block from "(deferred)" to live; update the §10 rollout table row.
  • Update the callback HTML page: remove the "⚠ Not active yet" warning once the middleware is live.

Blocks

This is the gate for the OAuth flow being end-to-end usable. The full stack (#58 design, #209 scaffold, #210 token store, #213 tool swap, #216 install/callback) is functionally inert for end users until this lands.

Spawned from PR #216 round-3 review (CTO agent — flagged the missing tracking issue).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions