- Exclude auth/ and reset-password from the legacy-host redirect (#1092):
email links sent before the cutover carry a PKCE code or recovery
session whose cookies live on app.gnubok.se; forwarding them to the
new domain breaks password resets and signup confirmations clicked
after the flip. login/MFA stay redirected on purpose: a usable login
page on the legacy host would establish sessions there and loop.
Exclusion pattern extracted to lib/domains/legacy-redirect.ts with a
unit test pinning the behavior.
- Clean up ephemeral oauth state rows (incl. oauth_user_id) when the
SKV token exchange fails (#1090): identity data must not outlive the
flow; best-effort so cleanup failure never masks the user-facing error.
- Assert the stored user is still a member of the company before the
service-role storeTokens write (#1091): membership can be revoked
between /authorize and the callback, and RLS no longer backstops the
write. Checked before the exchange so the one-shot code is not burned.
Fixes#1090, fixes#1091, fixes#1092.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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>
* feat(mcp): kontoplan account tools + verifikat notes exposure
Two gaps reported by an MCP-driven user: no account management in the
API, and verifikat notes invisible to agents (they exist in the product
but MCP could neither read nor write them).
- add staged gnubok_create_account / gnubok_update_account (BAS 2026
prefill for catalog numbers; rename/VAT-default/SRU/activate via
update; both LOW risk reference data)
- add staged gnubok_set_voucher_note (notes-only annotation, legal on
posted entries per the 20260608120000 trigger carve-out) and return
entry_notes from gnubok_query_journal
- new pending_operations types create_account / update_account /
set_voucher_note (CHECK migration + validate companion, applied to
staging)
- tools/list payload ceiling 54K -> 56K (documented; wire contract,
descriptions trimmed first)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skatteverket): unstick BankID connect flow and stale connection views
- respond to the OAuth callback immediately and run the post-connect
refresh after the response (next/server after()): users no longer
stare at Skatteverket's consumed consent page for up to 40s
- open the consent flow in a full tab instead of a 600x750 popup that
hid the approve button below the fold
- disable connect buttons while the OAuth tab is open (parallel flows
overwrote oauth_state + the PKCE verifier) and recover via a
closed-tab watcher plus a delayed status refetch
- persist MISSING_SCOPE token health from the post-connect sync and
show an actionable "approve all permissions" notice
- refetch connection state on tab visibility (settings connect panel,
enable-banking panel, /skattekonto) so a connect completed in another
tab or after a mobile app-switch shows up without a manual reload;
fix /skattekonto never clearing its not-connected state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(article-form): add article number field with validation to ArticleForm
* feat(account): enforce account type consistency with BAS class and add validation
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>