Files
accounted/lib/reports
Mattsson f047c3d7d1 fix(skatteverket): finish the BankID consent on the initiating origin, bound to the initiating user (#2373)
* fix(skatteverket): finish the BankID consent on the initiating origin, bound to the initiating user

The Skatteverket OAuth callback answered NEXT_PUBLIC_APP_URL regardless of
where the flow started, so on a white-label brand domain the popup's
postMessage was dropped and the fallback redirect landed on the wrong
origin without a session. On hosted, the initiator check from #2155 was
bypassed by design because the registered callback host carries no app
cookies, so a lured victim's BankID-authorised tokens could be stored
under the user who started the flow.

Flow state moves from six per-company extension_data keys to one
oauth_flows row per flow (migration 20260907120000), consumed atomically.
Hop 1 on the registered OAuth host consumes the state, stashes the
provider code or error encrypted under a separate handoff id and 302s to
the recorded origin; hop 2 there claims the handoff bound to that origin,
requires the initiating user's session, re-checks membership and
exchanges the code. Error pages keep the tab open. The self-hosted
single-hop and the connector broker branch keep working. The hosted
no-session exception, the legacy cookie-user fallback and the optional
PKCE verifier are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1YDNadz81eWo94j115bhH

* fix(skatteverket): decide the callback hop by host, close the tab when the flow is unknown

Skeptic findings on #2373. The hop comparison and the handoff claim used
the request origin including its scheme, which Next derives from
x-forwarded-proto; a self-hosted proxy that forwards Host without it (or
rewrites Host to the upstream address) made every connect end in a state
error. Hops are now compared by host only, and the handoff is claimed for
the validated origin the host resolves to, scheme from configuration.

Error pages answered before the flow row is known (unknown, expired or
replayed state or handoff) post to a guessed origin that a brand opener
never hears; they now close the tab so the panels' closed-tab watcher
resets them instead of leaving Connect disabled.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1YDNadz81eWo94j115bhH

* test(skatteverket): mock resolveBrandResultByHost for the merged login-redirect resolver

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1YDNadz81eWo94j115bhH

* fix(skatteverket): bind the initiator before the flow is spent

Superagent P2 on #2373: hop 2 deleted the handoff before the session and
membership checks, so a signed-out or wrong-user arrival burned a live
consent. The finishing hop now peeks the row for its initiator, binds the
completing session to it, and only then consumes atomically. A
session-less arrival is sent to /login on the initiating origin and
resumes into the same callback URL; a different user is refused with the
row left claimable for the initiator. The handoff TTL is five minutes so
a sign-in fits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1YDNadz81eWo94j115bhH

* fix(skatteverket): check membership before the flow is spent, answer the callback page on a failed mint

Second review cycle on #2373. Superagent: the company-membership check
ran after the consume, so a revoked initiator burned the provider code on
the way to being refused; it now runs inside the pre-consume binding.
CodeRabbit: a failed handoff mint escaped as a framework error page the
opener never hears; it now answers the callback error page on the
initiating origin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1YDNadz81eWo94j115bhH

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 16:43:38 +02:00
..
2026-07-12 19:14:12 +02:00
2026-07-23 16:16:55 +02:00
2026-07-28 19:50:16 +02:00
2026-07-08 09:54:46 +02:00