Files
accounted/lib/providers/fortnox/__tests__/oauth.test.ts
T
MattssonandClaude Fable 5 98612fb0ac fix(providers): stop requesting unapproved Fortnox scopes that broke every connect (#1549)
PR #1541 added archive and connectfile to the Fortnox DEFAULT_SCOPES for
the voucher attachment import, but the registered Fortnox app does not
have those scopes approved in the Fortnox Developer Portal. Fortnox
rejects the authorize request with invalid_scope before login, which
broke every Fortnox connect in production within minutes of the deploy
(verified in Vercel runtime logs).

Remove the two scopes from the connect request; the attachment import
logic from #1541 stays fully intact and already degrades gracefully:
a 403 becomes PROVIDER_DOCUMENT_SCOPES_REQUIRED with a reconnect
follow-up card. Re-add the scopes once the portal registration has them
approved.

Also add charset=utf-8 to the OAuth callback HTML responses: without it
browsers render the Swedish error text as Latin-1 mojibake.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 01:40:20 +02:00

26 lines
1.0 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { buildFortnoxAuthUrl } from '../oauth';
describe('Fortnox OAuth scopes', () => {
// Pins the 2026-08-13 incident fix: the registered Fortnox app does not
// have the archive/connectfile scopes approved, and requesting a scope the
// app lacks makes Fortnox reject the authorize request with invalid_scope
// before the user can even log in. Do not add them back here until the
// Fortnox Developer Portal registration includes them.
it('does not request archive or connectfile until the Fortnox app has them approved', () => {
const url = new URL(
buildFortnoxAuthUrl({
clientId: 'client-id',
clientSecret: 'client-secret',
redirectUri: 'https://accounted.example.test/callback',
}),
);
const scopes = new Set(url.searchParams.get('scope')?.split(' ') ?? []);
expect(scopes).toContain('bookkeeping');
expect(scopes).not.toContain('archive');
expect(scopes).not.toContain('connectfile');
});
});