whisper-money/tests
Víctor Falcón 2041181dc2
fix(mcp): serve OAuth authorize on a dedicated host so the PWA can't capture it (#709)
## Problem

Connecting the MCP connector from **ChatGPT on Android** fails. The
installed Whisper Money PWA is a Chrome **WebAPK** that auto-verifies as
an Android **App Link handler** for the whole app origin (manifest
`scope: "/"`), so `https://whisper.money/oauth/authorize` gets routed
**into the app**. Once inside the standalone app, the redirect back to
the OAuth client can't complete → the connection fails. (Claude works
because it opens OAuth in a Custom Tab.)

Confirmed on an Android emulator: the WebAPK shows `AutoVerify=true`,
`whisper.money: verified`. DB evidence: ChatGPT registers + reaches
consent (auth codes issued) but never exchanges a token.

## Why not `handle_links`

`handle_links: "not-preferred"` (tried in #707, reverted in #708) is
**origin-wide** — it would push *every* `whisper.money` link (bank-auth
callback, email verification, shared deep links) to the browser, not
just `/oauth`. We want links to keep opening the installed app.

## Fix (surgical)

Move the OAuth **authorization server** to a dedicated host outside the
PWA scope. `config('mcp.authorization_server')` becomes env-driven
(`MCP_AUTHORIZATION_SERVER`); in prod → `https://oauth.whisper.money`
(DNS already points at the same app).

Every endpoint derives from the request host (no forced root URL), so
pointing the auth server at the subdomain makes `issuer` +
`authorize`/`token`/`register` all resolve to `oauth.whisper.money` —
**same origin as each other**, no cross-origin metadata mismatch. The
protected resource (`/mcp/oauth`) and **all other app links stay on
`whisper.money`**, so deep-linking into the app is fully preserved. Only
the OAuth flow leaves the app — into the browser, where the round-trip
completes.

## Activation (after merge + deploy)

1. Set `MCP_AUTHORIZATION_SERVER=https://oauth.whisper.money` in prod
env, redeploy.
2. I'll curl the discovery chain to confirm it resolves to the
subdomain.
3. Test the ChatGPT connect on a real phone.

Safe until step 1: env unset → `authorization_server` stays `null` →
current behavior. No effect on local/dev.

## Tests

Added a Pest test: with `mcp.authorization_server` configured,
protected-resource metadata advertises the dedicated host and
auth-server metadata (fetched from that host) keeps `issuer` + all
endpoints on it. App has no `TrustHosts` restriction (already serves the
subdomain) and `SESSION_DOMAIN=null` (host-only cookies — subdomain gets
its own session, no security downgrade).
2026-07-21 13:51:03 +02:00
..
.pest Speed up PR CI browser path (#365) 2026-05-07 20:40:13 +01:00
Browser feat(onboarding): auto-enable AI for connected banks, ask the rest (#618) 2026-07-01 07:26:52 +00:00
Feature fix(mcp): serve OAuth authorize on a dedicated host so the PWA can't capture it (#709) 2026-07-21 13:51:03 +02:00
Fixtures refactor: consolidate duplicated financial calculations (#643) 2026-07-04 22:26:44 +02:00
Performance feat(transactions): make new-transaction marker cross-device (#611) 2026-06-29 19:11:37 +02:00
Support test(open-banking): e2e coverage for Enable Banking connection flows (#509) 2026-06-09 11:58:50 +02:00
Unit refactor: extract duplicated money formatter into App\Support\Money (#680) 2026-07-15 09:47:53 +02:00
Pest.php chore: harden Inertia boundary, CI type-check, and test isolation (#640) 2026-07-04 18:57:58 +00:00
TestCase.php Remove encryption from browser tests and demo user (#129) 2026-02-17 11:45:27 +01:00
bootstrap.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00