* fix(tic): reject BankID signup when email is already registered (CWE-287)
Signup previously linked BankID to any pre-existing profile matching the
submitted email. Because BankID proves identity but not email ownership,
an attacker who knew a victim's email could bind their own BankID to the
victim's account and then log in via BankID (which skips TOTP MFA).
Now signup returns 409 account_exists; the register page shows a Swedish
error toast and redirects to /login so the user can authenticate with
their password first and link BankID from settings (via the authenticated
/bankid/link route that already exists).
Covered by new bankid-complete.test.ts with an explicit regression test
asserting no side-effects occur on the account_exists path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(bookkeeping): prevent duplicate registration entries for supplier invoices
The invoice-inbox convert flow creates the registration journal entry
inline and then emits supplier_invoice.confirmed. The core handler was
also creating one, producing a second voucher on 2440/2641/expense and
overwriting registration_journal_entry_id on the row.
Two guards in the core handler:
- Payload guard: skip if supplierInvoice.registration_journal_entry_id
is already set (fast path for callers that include it in the payload).
- DB re-fetch guard: re-read the row and skip if it has been linked
since the payload was built (handles stale-payload callers).
The inbox extension now stamps registration_journal_entry_id onto the
in-memory invoice before emitting so the fast path trips.
Also fixes a latent bug where the handler filtered company_settings by
userId instead of companyId, which would have selected the wrong row
(or none) on multi-company accounts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(events): write company_id on event_log rows and refuse unscoped events
event_log has a company-scoped SELECT RLS policy, so rows written with
NULL company_id are invisible to every user — effectively silently
dropped from the automation feed. The handler now reads companyId from
the payload (all persisted event types mandate it in TS) and includes
it in both single and batch inserts.
If a caller ever bypasses the type system and emits without companyId,
the handler logs an error and skips persistence rather than writing a
poisoned row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR review nits (event-log meta strip, redirect timer)
- event-log-handler: rename stripUserId → stripMetaFields and also drop
companyId from the stored data JSONB so it isn't duplicated alongside
its dedicated column.
- register page: drop the 1500ms setTimeout before router.push('/login')
on the account_exists branch — the timer had no cleanup and fired a
stale-closure push if the component unmounted first. The toast survives
the route change via the root layout's Toaster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: multi-series SIE import, reusable FiscalYearSelector, library templates in picker
- SIE import preserves each voucher's source series (B/C/I/V/...), essential
for Fortnox migrations where series carry semantic meaning (kundfakturor,
inbetalningar, etc.). Target numbering still goes through next_voucher_number
per series; source (series, number) is stored in the migration mapping for
BFNAR 2013:2 audit trail.
- Execute route reads company_settings.default_voucher_series as the fallback
for vouchers arriving without a series (SIE4I).
- Extract shared FiscalYearSelector component; adopt in /reports and
/bookkeeping.
- Transaction TemplatePicker now surfaces user-created library templates
(company + team scope) alongside the static registry, with a helper to
convert simple library templates into the BookingTemplate shape.
- Exclude 8999 "Årets resultat" from income statement financial section and
monthly breakdown so year-end closing entries don't cancel the net result.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: skip Bokio SIE regression when fixtures are absent
/dev_docs is gitignored (contains anonymised customer exports), so the
integration test can't find its input files in CI. Gate the suite on
fixture presence so it still runs locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address Greptile review feedback
- convertLibraryToBookingTemplate: default entity_applicability to 'all'
when the source template has no entity_type, so TemplatePicker doesn't
silently hide it for companies with a set entity type.
- FiscalYearSelector: fire onReady in the no-company early-return branch
so consumers (e.g. ReportsPage) don't get stuck in a loading skeleton
while the company context is still hydrating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: arcim inbox + smart-match extension + commit metadata
Three threads, all gated off in extensions.config.json (invoice-inbox and
inbox-smart-match are not in the enabled list for this PR).
invoice-inbox: Gmail OAuth -> Resend Inbound (v2.0.0)
- Remove gmail-scanner / gmail-helpers
- Add resend-inbound.ts (webhook verify, attachment fetch) and
inbox-provisioning.ts (per-company @arcim.io address with rotation)
- Replace /gmail/* routes with /inbox/address and admin-only /inbox/rotate
- Workspace UI: card layout + MatchBlock surfacing AI transaction matches
- classify-document: tightened discount/total prompt; cap confidence at
50% when line items do not reconcile with amount_incl_vat
- Manifest requires RESEND_API_KEY, RESEND_INBOUND_DOMAIN,
RESEND_INBOUND_WEBHOOK_SECRET
inbox-smart-match (new extension)
- Event-driven AI matching of receipts to bank transactions
- Listens on inbox_item.classified (match now) and transaction.synced
(retro-match receipts waiting for a transaction)
- Uses service-role client; processing_history append is scoped by
company_id from the event payload
commit metadata + audit plumbing
- journal_entries gains commit_method and rubric_version columns
- commit_journal_entry RPC accepts both (BFNAR 2013:2 behandlingshistorik)
- processing-history PII detector strips UUID-shaped substrings before
personnummer pattern matching (UUIDs were triggering false positives)
- New generic inbox_item.classified event
Migrations
- arcim_inbox: company_inboxes table, resend_email_id, email_body_text,
auto-provision trigger, drops obsolete email_connections
- journal_entry_commit_metadata: new columns + updated RPC
- inbox_attachment_composite: resend_attachment_id + composite unique index
- inbox_smart_match: correlation_id, match_reasoning, expanded match_method
CHECK, pending-match and correlation indexes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: invoice inbox extension — conversion, workspace UI, Gmail UX
Complete the invoice-inbox extension with full end-to-end flow:
- Add POST /items/:id/convert route to create supplier invoices from
classified inbox items, with accrual journal entry and document linking
- Add PATCH /items/:id/reject route to dismiss non-relevant items
- Add workspace UI at /e/general/invoice-inbox with items table,
status filtering, convert dialog, and match confirmation
- Add Gmail connection banner (connect/disconnect/status) in workspace
- Add one-click supplier creation from AI-extracted data
- Add transaction auto-matching with fuzzy name + currency-aware amount
- Add event emission (received, extracted, confirmed) on classification
- Redirect OAuth callback to workspace instead of /settings/banking
- Fix extension catch-all body clone for POST routes with path params
- Fix duplicate Löner nav entry from salary module merge
- Remove summary cards from expenses and supplier invoices pages
- Fix supplier-invoices/new amount input (valueAsNumber → Controller)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Greptile review — company_id filters, currency guard, skipAuth clone
- Add company_id filter to reject route update (defense in depth)
- Add company_id filter to document_attachments journal entry link
- Guard sekMatch with tx.currency === 'SEK' to prevent false matches
- Clone request in skipAuth branch for consistency with auth branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove AI-dependent extensions (ai-chat, ai-categorization, receipt-ocr,
invoice-inbox) and their infrastructure (lib/ai/*, ai-consent, LangChain/
Anthropic/OpenAI deps) to simplify core and reduce bundle size.
Restructure monolithic settings page into dedicated sub-pages (company,
bookkeeping, invoicing, tax, banking, api, account, team, templates) with
shared layout and sidebar navigation.
Add atomic commit_journal_entry RPC so voucher number increment and status
update happen in a single transaction — prevents burned numbers on constraint
failures. Add continuity check report and voucher gap explanation tracking.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update BAS account catalog with comprehensive SRU codes and K2 flags
- Add currency revaluation service with tests and API route
- Add expenses page and account deletion API
- Enhance booking templates with new patterns and improved tests
- Improve transaction categorization with template picker and description matching
- Polish dashboard, onboarding, import, and transaction UIs
- Refactor year-end service for multi-step closing
- Move SRU generator to ne-bilaga, remove standalone SRU export
- Remove unused dev docs, mock data, and extension hooks
- Add invoice delivery note sequences migration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>