* feat(domains): dual-domain cutover to app.accounted.se
The user-facing app moves to app.accounted.se while app.gnubok.se stays
alive for machine traffic (MCP connectors, API keys, third-party OAuth
callbacks, webhooks, crons), so no third-party callback registration is
on the critical path.
- next.config: host redirect app.gnubok.se -> NEXT_PUBLIC_APP_URL for
page traffic only (/api, /.well-known, /_next excluded). Arms itself
only once NEXT_PUBLIC_APP_URL leaves the legacy host, so merging this
is inert and the cutover is a pure env flip + redeploy.
- skatteverket: redirect_uri pinned via NEXT_PUBLIC_SKV_OAUTH_BASE_URL
(Utvecklarportalen registration is slow to change); the OAuth callback
now resolves the flow from the state token + stored oauth_user_id via
the service client instead of session cookies, which no longer exist
on the OAuth host. Legacy same-domain flows fall back to the session.
- popup listeners (SkatteverketConnectPanel, AGIPanel) accept postMessage
from the pinned OAuth origin; event.source identity check unchanged.
- /.well-known discovery docs reflect the allowlisted request host so
existing MCP connectors on app.gnubok.se keep a self-consistent
issuer/resource after the flip; spoofed hosts fall back to canonical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: log dual-domain cutover decision
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): recency-bound SKV state lookup, exact localhost match in discovery allowlist
- The oauth_state lookup now only considers rows updated in the last 10
minutes: bounds how long a leaked/phished authorize URL stays
completable, keeps the row set far below PostgREST's 1000-row cap, and
surfaces query errors instead of misreporting them as CSRF.
- resolveDiscoveryBaseUrl matches localhost/127.0.0.1 exactly; the
prefix check reflected spoofed hosts like localhost.evil.example.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>