* fix(mcp-oauth): allowlist Cursor's OAuth callbacks so its dynamic registration succeeds
Cursor (IDE, CLI, and Grok Bot on top of it) registers three redirect URIs
in one /register request: cursor://anysphere.cursor-mcp/oauth/callback,
https://www.cursor.com/agents/mcp/oauth/callback and
http://localhost:8787/callback. Only the loopback matched a built-in
pattern and /register fails the whole set on any unknown URI, so every
Cursor connection to the URL we hand out in Settings died with
"Redirect URI not allowed". Users cannot self-register the cursor://
form either (the settings panel requires https).
Add a built-in `cursor` provider with the two non-loopback callbacks as
exact matches (no cursor.com prefix), name it "Cursor (Anysphere)" on
the consent page, list the pre-approved clients in the OAuth clients
settings text (sv + en) and the mcp-server rules, and cover the
register, allowlist and consent paths with tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBFeTvQXgMCNXR6fG9drff
* fix(mcp-oauth): show the cursor:// deeplink unverified and let CSP pass its post-consent redirect
Review findings on #2225, one pass:
- Skeptic (correctness), REFUTED: new URL('cursor://...').origin is the
string "null", so the consent page emitted form-action 'self' null and
Chromium would block the 303 to the deeplink after Allow. The header
now uses the scheme-source (cursor:) when the origin is opaque; a test
pins the header on the cursor:// URI.
- Skeptic (security), CodeRabbit (Major) and Superagent (P2): a custom
scheme can be claimed by any local app (RFC 8252 section 8.4), so it
must not be presented as a vendor-verified callback. The deeplink is
its own provider, cursor_deeplink, rendered "Cursor (Anysphere)" with
the localhost tag "Din egen dator" and verified: false. The https
cursor.com callback keeps the verified label. A test pins that a code
minted without a code_challenge can never be exchanged, which is what
keeps a scheme hijack from turning into a token.
- CodeRabbit (Minor): the rules doc now says the Grok callback matches
with or without the trailing slash.
- Regression skeptic: docs/WHITELABEL.md listed only Claude and
localhost and pointed at the wrong file; now lists the built-ins and
points at lib/auth/oauth-allowlist.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBFeTvQXgMCNXR6fG9drff
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* chore: remove Sentry, consolidate migrations, add test coverage
Remove @sentry/nextjs and all Sentry integration code — error tracking
now handled by Recapt. Consolidate 22 incremental migrations into a
single schema sync migration. Add 6 new test suites (auth, invoice
matching, VAT rules, opening balances) and extend report tests with
edge cases. Update Docker image name to gnubok, sync crontabs and
extension presets, fix CSP missing space, simplify journal entry
missing-document dialog.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove viewer bank import migration never applied to production
20260413150000_viewer_bank_import_permissions.sql (PR #234) was merged
to main but never applied to the production database. It references
current_active_company_id() which does not exist in production either.
This breaks fresh installs and Supabase preview branches because the
migration runs before the consolidated schema sync.
Remove it so the migration chain matches production. The viewer bank
import RLS policies should be re-added in a future migration alongside
the helper functions they depend on.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct delete policies for tables without company_id column
Seven tables in the generic delete-policy loop don't have a direct
company_id column, causing fresh installs to fail with "column
company_id does not exist". Fix by moving them out of the loop:
- invoice_items, journal_entry_lines, receipt_line_items,
supplier_invoice_items → join through parent table
- extension_toggles, notification_settings, push_subscriptions →
user-scoped (auth.uid() = user_id)
All policies match their existing production definitions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>