Three small defects found underneath error clusters that are themselves benign.
bounceToAuth returned a bare NextResponse.redirect and never copied the cookies
off supabaseResponse, so the Set-Cookie headers that clear a dead session were
thrown away on the first bounce and the browser replayed the dead refresh token
once more on /login. That doubled both the GoTrue 400s and the log volume. The
AuthApiError itself is left alone: it is correct session-expiry handling that
auth-js logs from inside node_modules, and getUser() returns it as a value.
"The destination stream closed early." is a client disconnecting mid-stream,
produced inside React's Flight server. Next's own isAbortError filter does not
recognise React's cancel error, so instrumentation.ts reported it to PostHog
Error Tracking as a real exception against real users' session replays and paid
an awaited flush on an otherwise-healthy request. A narrow predicate now
early-returns before PostHog is touched. This cannot remove the line from
Vercel's runtime-error table, which is fed by Next's stderr.
other-account-hint.ts issued an unfiltered journal_entries probe on the
blocking Hem render path, inside a render Promise.all: roughly 1 in 20 Hem
loads waited an extra 2.5 s for an advisory nudge. An unfiltered probe on a
multi-tenant table is also a correctness smell. It is now company-scoped and
off the blocking path.
Claude-Session: https://claude.ai/code/session_016ifKg6Ec67A39oxfGPU1yc
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(auth): surface duplicate-account traps around BankID login
Three escape hatches for the stale-duplicate-account trap (#1231, the
Chillen support case): a user whose BankID resolves to an abandoned
account got an empty app with no hint that their real bookkeeping
lives in another account.
- check-org-number: new exists_elsewhere signal (service role, reduced
to one boolean) + a warn chip in the onboarding journey when the org
number already exists in an account the user is not a member of.
- Hem: one AttnLine under the greeting when the whole account has zero
journal entries but a same-orgnr company elsewhere has real
bookkeeping, with a sign-out action. Common case costs one indexed
existence probe.
- scripts/support/unlink-bankid.ts: dry-run-by-default support action
that unlinks a BankID identity (delete + app_metadata clear +
append-only SECURITY_EVENT audit_log row). Replaces the raw SQL used
to resolve the original ticket.
Closes#1231
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): harden unlink script and paginate hint queries per review
- other-account-hint: fetchAllRows() on both company listings (PostgREST
1000-row cap; byrå users can hold many memberships); the journal probes
stay limit(1) existence checks.
- unlink-bankid: audit_log row is written BEFORE the delete so a partial
failure can never delete without a trace; context queries fail closed
instead of rendering an unknown account as empty; stdout no longer
prints the personnummer hash or ciphertext (the unsalted hash is
brute-forceable over the personnummer space); record_id now carries the
identity row id and the snapshot includes id + linked_at.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>