70e893b8d4c2d5348e7c6f7ab7407fa1b2eabbed
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
237b77a366 |
feat: custom inbound mail domains, rot/rut payout file, invoice email texts, security hardening (#878)
* fix(security): guard MCP test keys, RLS role gate + voucher RPC guards, /api MFA gate, deps - MCP: force dry-run / block writes for test-mode API keys in tools/call (extensions/general/mcp-server) - DB: current_user_can_write role gate on write policies (40 tables) + tenant guards, SET search_path, REVOKE anon on commit_journal_entry / next_voucher_number / detect_voucher_gaps (migration 20260702093000) - Middleware: MFA (AAL2) gate on cookie-authenticated /api routes via apiPathSkipsMfaGate - Deps: npm audit fix clears mailparser/linkify-it/nodemailer/svix/uuid highs; xlsx -> SheetJS 0.20.3 Adds unit + pg-real tests. Does not touch in-progress ROT/RUT or invoice-email-texts work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(invoices): rot/rut begäran om utbetalning — HUS XML (V6), payout tracking + settlement, MCP tool Generates Skatteverkets begäran-om-utbetalning file (schema V6) from paid ROT/RUT invoices — no submission API exists, the file is uploaded manually at skatteverket.se. Headless by design for now: API routes + MCP tool (gnubok_generate_rot_rut_file), no UI surfaces. - lib/invoices/rot-rut-file.ts: pure XML generator with deterministic per-invoice blockers (hours, work type, personnummer, property info, mixed rot+rut, XSD limits) + 31 January deadline warnings - rot_rut_payout_requests(+items) tables: one active begäran per invoice (DB triggers incl. reactivation guard), RLS, audit, pg-real tests - Settlement: POST /settle books debit 1930 / credit 1513 via the engine (source_type rot_rut_payout); partial payouts → partially_paid - Work-type lists corrected against Begaran.xsd: IT-tjänster is rut-only, snöskottning/tillsyn/tvätt added (schablontjänster utfört-only) - Fix: invoice-level fastighetsbeteckning was validated but never persisted — now stamped onto rot lines in build-invoice-write; API accepts bostadsrätt pair (lägenhetsnr + BRF orgnr, editor UI deferred) - invoice_items.brf_org_number migration + MCP scope invoices:write Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(invoices): per-company editable invoice email texts Add an "E-posttexter" section under Settings -> Fakturering where the subject, greeting, body and sign-off of the standard invoice email can be customized per company in Swedish and English. Fields pre-fill with the standard texts and only diffs from the standard are stored (company_settings.invoice_email_texts JSONB), so future improvements to the stock wording still reach companies that have not customized. Each field has a reset-to-standard button; cleared fields snap back. Texts support a fixed placeholder set (invoice number, customer name, first name, company, due date, amount) substituted at send time in a single pass; unknown placeholders stay literal. Custom texts are HTML-escaped after substitution, newlines become <br> in the HTML variant, and subject lines are flattened to a single header line. Overrides apply to standard invoices only - credit notes, proforma and delivery notes keep the stock texts. All send paths (UI, v1 API, MCP approval, recurring) pick the texts up via the existing settings row. The Zod schema half of this change (InvoiceEmailTextsSchema in lib/api/schemas.ts) was inadvertently included in 8291f745. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(documents): accept PDFs with preamble before %PDF- header, surface content rejections as 400 detectFileMagic required the %PDF- signature at byte 0 (BOM aside), rejecting genuine PDFs that carry a leading newline or junk bytes — files every ISO 32000 reader opens fine. Now scan the first 1024 bytes for the signature, matching real-reader behavior. Image types stay strict at offset 0 to keep the anti-placeholder defense tight. Magic-byte rejections were also mislabeled as DOC_UPLOAD_STORAGE_FAILED (500 'Filen kunde inte sparas'), blaming storage for a client-side file problem. Both upload routes now map them to a new DOC_UPLOAD_INVALID_CONTENT (400) with an accurate message. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(bookkeeping): full keyboard flow for manual journal entry Enter now drives the whole verifikat flow: verifikationstext drops into the first row missing an account, konto commits advance to debet, Enter on an empty debet hops to kredit, and an entered amount jumps to the next row. Once the voucher balances, Enter opens the review (unchanged gate) and the auto-focused confirm posts it — including through the no-underlag warning dialog. Escape in the inline review goes back to the form. Also fixes an Enter footgun in AccountCombobox: a bare Enter on a freshly focused field no longer selects the first account in the list — selection now requires typing or arrow navigation; otherwise Enter re-commits the current value or bubbles to the form-level handler. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add custom inbound domains management for companies - Implemented functionality to allow companies to claim and manage their own inbound email domains via Resend's API. - Created a new table `company_inbound_domains` to store domain information, including status and DNS records. - Added necessary RLS policies to restrict access based on user roles (owner/admin). - Developed functions for domain normalization, validation, claiming, verification, and removal. - Implemented webhook handling for domain status updates from Resend. - Added comprehensive tests for RLS, constraints, and triggers related to the new domain management feature. * fix: address PR #878 review findings and CI failures - migrations: drop the ai_usage_tracking policy block from the role-gate migration — the table was removed by 20260504120000_remove_ai_subsystem and only lingers on staging as drift; a from-scratch chain (pg-real, Supabase preview) failed on it - invoice-inbox: never flip a custom domain to verified off a domain.updated webhook alone — confirm the receiving capability with Resend first (fail-closed); normalize both sides of the orphan-adoption domain match - rot/rut: block files where begärt belopp exceeds what the buyer paid (DEDUCTION_EXCEEDS_PAYMENT); tighten brf_org_number validation to real orgnr shapes; parameterize the settlement bank account (19xx, default 1930) - rot/rut routes: log acting user on financial mutations, stop swallowing item mirror errors, narrow response projections (no customer ids through the invoice join); document the deliberate inline-XML decision - documents: stop echoing raw storage-layer error messages to clients Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: round-2 CI + compliance findings on PR #878 - migrations: the role-gate migration targeted automation_webhooks, which 20260515170000_webhooks_v2 renamed to webhooks on the canonical chain (staging kept the old name — drift); gate public.webhooks instead, dropping legacy schema-sync policy names defensively. Restore the 20260623130000 owner fallback in next_voucher_number that the stale copied-verbatim body silently reverted (caught by engine.pg locally). Full migration chain verified from scratch against supabase/postgres:15. - mcp: bump the tools/list payload ceiling 44K -> 45K — main's #877 qualified-identifier schemas plus this branch's rot/rut tool crossed the ceiling only in combination; documented in the test's history log. - rot/rut: refuse partial settlement before Skatteverkets beslut is recorded (would bypass the PATCH lifecycle and strand the request); block zero-kronor ärenden (ZERO_DEDUCTION); require sekelsiffra 16 on 12-digit brf orgnr in both schema validation and normalizeBrfOrgNr Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: rename branch migrations off main's colliding versions After the merge with main, two versions were shared by two files each (20260702100000: rot_rut_payout_requests vs company_settings_dimensions_ enabled; 20260702130000: invoice_email_texts vs pending_operations_add_ create_dimension_value). psql-based CI applies by filename and doesn't care, but Supabase branching records migrations by version (PK) — the second file with the same version breaks the preview with a schema_migrations_pkey duplicate. Neither branch migration is version- recorded on staging or prod, so renaming to fresh 20260703 versions is safe; nothing between the old and new positions depends on these objects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(security): scope the /api MFA-gate bypass to real Bearer-auth surfaces Any Authorization header — attacker-controlled — used to skip the AAL2 gate for every /api route, so a stolen-password AAL1 cookie session could reach cookie-authenticated routes (which ignore the header) by attaching `Authorization: x`. The skip is now scoped to the surfaces whose auth contract IS the header (/api/v1 API keys, the MCP endpoint's OAuth tokens); pure Bearer callers elsewhere (cron secret, signed webhooks) carry no cookie session and were never touched by the gate, which only fires for cookie users. Superagent P2 on PR #878. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: normalize path separators in dimension statutory guard scan The route scan compared walked file paths against a POSIX-path allowlist, so the suite failed on Windows (backslash separators) while passing on Linux CI. Normalize the scanned paths to forward slashes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4253afc343 |
fix(company): validate user_preferences write when switching company (#708)
Fixes #701. setActiveCompany upserted active_company_id without checking the result, then set the gnubok-company-id cookie unconditionally. A failed write — including an RLS-filtered UPDATE, which affects zero rows without raising an error — looked like a successful switch: switchCompany returned {}, the UI hard-reloaded, and middleware (which reads user_preferences, not the cookie) resolved the old company. - setActiveCompany now verifies the upsert with .select().single() and throws a typed CompanyContextError ('not_member' | 'persist_failed'); the cookie is only set after the write is confirmed, so it can no longer diverge from the database. - switchCompany logs the failure and returns distinct error codes instead of reporting every failure as a permissions problem. - CompanySwitcher now shows a destructive toast on failure (it previously failed with no feedback); BankIdCompanyPicker translates the codes. Messages added to sv/en under company_switcher and select_company. - The remaining fire-and-forget user_preferences writers (middleware fallback write-back, team invite accept, auth callback invite accept) now check and log errors; non-fatal by design since each has a working fallback path. - New tests cover every failure mode, including cookie-not-set on a failed write and the silent zero-row write caught by the read-back. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e4488a900b |
feat: add user locale preference to user_preferences table (#555)
* feat: add user locale preference to user_preferences table
- Introduced a new column 'locale' in the user_preferences table to store per-user UI language preferences.
- Added a CHECK constraint to ensure only supported locales ('sv', 'en') are allowed.
- Triggered a schema reload notification for the changes.
chore: declare CSS module support in TypeScript
- Added a declaration for CSS modules in globals.d.ts to enable TypeScript support for importing CSS files.
* feat: add Swish as an invoice payment method in company settings
|
||
|
|
cc351158f8 |
Invoicing & account-security polish bundle (#550)
* feat: invoicing & account-security polish bundle Five independent improvements bundled to ship together: - BankID/password lockout fix: BankID-only users could enroll MFA and brick themselves (Supabase requires AAL2 to change password or unenroll MFA, and AAL2 needs a password sign-in). New app_metadata.has_password flag tracks this; middleware gates /mfa/enroll behind it, /account/set- password is the unlock path, SecuritySettings shows a banner, and /api/account/password is the single write path that flips the flag. Backfill script for existing users. - Swish invoice payment method: company_settings.swish + invoice_show_swish columns, validation in lib/api/schemas.ts (accepts 123XXXXXXX företag or 07XXXXXXXX mobile, strips whitespace/hyphens), rendered on invoice PDFs. - Send-reminders kill switch: per-company company_settings.send_invoice_ reminders toggle in PdfPrintSettings/Automatisering. Reminder processor also tightened: positive status allowlist (sent + overdue) so terminal statuses can never match; skip when customer already responded via reminder link; race-window re-check before send. - First-invoice logo prompt: one-shot dialog when creating the first invoice without a logo (issue #520). Self-limits via head-only count. - SIE export opening-balance fallback: route IB through getOpeningBalances so the compute_prior_opening_balances RPC supplies #IB after multi-year imports where opening_balance_entry_id is intentionally NULL. Previously #IB silently went to zero and #UB collapsed to current-period movements. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(account-polish): address PR review feedback - BankID-link path (extensions/general/tic/index.ts): read-merge-write app_metadata instead of passing { bankid_linked: true } alone. updateUserById REPLACES app_metadata wholesale, so the previous code would have wiped has_password for any user who later linked BankID, causing the set-password banner to (incorrectly) reappear and blocking the standard MFA enrollment button. The comment is now corrected. - Middleware (lib/supabase/middleware.ts): thread inner returnTo through the /mfa/enroll → /account/set-password redirect so the user lands on their original destination after the full chain completes, not on /. - safeReturnTo helper (lib/auth/safe-return-to.ts): replace the starts-with-/-but-not-// guard on mfa/enroll and set-password pages. The previous guard let /\evil.com and /@evil.com through. The new helper parses against a synthetic base origin and verifies it matches. - set-password page (app/(auth)/account/set-password/page.tsx): remove CLAUDE.md design system violations — bg-gradient-to-b on page bg, inline shadow-md style on the card, space-y-5, font-medium on the h1, rounded-xl on the card. Flat surface, hairline border, font-display h1 per the design tokens. - Swish dedup (lib/payments/swish.ts): extract normaliseSwish() and isValidSwish() helpers and use them in lib/api/schemas.ts, components/settings/BankDetailsForm.tsx, and the invoicing settings page. Single source of truth for the regex. - Password route (app/api/account/password/route.ts): emit a structured success log so the audit pipeline can detect password-set events, not just failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
37ccda5cad |
feat(api): v1 invoice :send + bulk-create (Phase 2 PR-B-2b-3 + PR-B-2c) (#458)
* feat(api): v1 invoice :send + bulk-create action verbs (Phase 2 PR-B-2b-3 + PR-B-2c)
Combined chunk: ship the full :send pipeline and partial-success bulk
creation in one PR, plus include the dangling /reset-password middleware
fix that completes PR-455's password-recovery flow.
POST /api/v1/companies/:companyId/invoices/:id/send
Full send pipeline mirroring the internal route, hardened for the public
API surface: email-configured check, draft-only guard, cancelled /
delivery-note / credit-note / missing-moms_ruta rejections, customer
email check, company-settings fetch, F-series invoice-number allocation
(atomic at :send per ML 17 kap 24§ p.2, not at draft create), preflight
PDF render before number consumption, final PDF render, email send,
point-of-no-return status flip, BFL 5 kap journal entry, document
archival, invoice.sent event emit. Post-send failures (journal entry,
archive) surface via a `warnings` array rather than failing the response
— the invoice IS sent at that point. Dry-run validates the pipeline +
preflight PDF without allocating a number or hitting the provider.
Error codes: INVOICE_SEND_EMAIL_NOT_CONFIGURED (503),
INVOICE_SEND_NO_CUSTOMER_EMAIL / _CANCELLED /
_COMPANY_SETTINGS_MISSING (400), INVOICE_UPDATE_NOT_DRAFT (409),
INVOICE_SEND_PDF_RENDER_FAILED / _NUMBER_ASSIGN_FAILED (500),
INVOICE_SEND_PROVIDER_FAILED (502).
POST /api/v1/companies/:companyId/invoices/bulk-create
Batch create up to 50 invoices in a single call, sequential processing,
partial success: `{ results: [{ ok, request_index, data?, error? }],
summary: { total, succeeded, failed } }`. Per-item rollback on items
insert failure (delete the parent invoice row). Emits invoice.created
per success. Dry-run wraps results in a preview without inserting.
`all_or_nothing` is accepted but reserved for a future PR.
lib/supabase/middleware.ts
Add /reset-password bypass before the authenticated-user redirect so
password-recovery sessions don't bounce to '/'. This should have landed
in PR-455 — the `git add 'app/(auth)'` filter missed the middleware
file at lib/. Without this the recovery email link silently fails for
the recipient.
Tests: 14 new integration tests across both routes (happy path,
provider failure, scope rejection, draft-only guard, dry-run shape,
bulk partial-success, max-50 enforcement, validation error). Full suite
green (3207 passing, 1 unrelated pre-existing pg-real failure).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(api): address PR #458 review — :send + bulk-create hardening
Greptile P1: silent zero-row update after email delivery.
PostgREST returns { error: null } on 0-row UPDATEs. The post-email
status flip used `.eq('status', 'draft')` as an optimistic lock but
never inspected the row count, so a concurrent state change (race,
double-send from another session) would leave the DB row in 'draft'
while the response claimed 'sent' and the email was already gone.
Fix: `.select('id')` after the update and check `flipRows.length`;
on 0-row miss, push STATUS_UPDATE_FAILED warning AND change the
response status to 'draft' so the caller can reconcile.
Greptile P1: re-read error swallowed; invoice_number could vanish
from the response.
After ensureInvoiceNumber, the re-read query destructured only `data`,
silently dropping `error`. A transient connection failure would leave
`numbered` null and `finalInvoiceNumber` undefined; JSON serialization
would then omit the field, violating the documented response schema.
Fix: capture `reReadErr`, log a warning, fall back to typed.invoice_number
(which was just written by the RPC and is authoritative in-memory).
Apply the same fallback at the top-level `ok()` call.
Greptile P2 + Compliance Swarm V2.3 + Swedish-compliance kreditfaktura:
reject credit notes from :send.
The :credit endpoint creates credit notes atomically in 'sent' state
with their own number — there is no v1 path that produces a draft
credit note, so reaching :send with credited_invoice_id set is misuse
or manual DB editing. Allowing it would assign an F-series number to
a kreditfaktura (ML 17 kap 22–23§ require a distinct kreditfaktura
series and a back-reference that this route would not enforce). Fix:
reject with VALIDATION_ERROR pointing at /credit. Removes a stretch
of dead code (originalInvoiceNumber lookup, kreditfaktura filename
branch) that can never execute now.
Greptile P2: all_or_nothing: true silently treated as false.
A caller asking for atomic semantics must not get partial-success
behaviour with no runtime signal. Fix: reject with new
NOT_IMPLEMENTED error (501) plus a details.field pointer. Schema
still accepts the flag for forward compatibility once a DB-side RPC
ships. New error code added to lib/errors/structured-errors.ts.
Tests: 3 new integration cases (credit-note rejection, status-flip
no-op warning, all_or_nothing 501). Suite green: 3218 passing.
Build clean, lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(api): PR #458 follow-up — defense-in-depth + comment precision
OWASP V8.2.1 (bulk-create): use DB-returned customer.id at insert time
instead of input.customer_id. The .eq() pair already enforces company
scoping at fetch, but echoing the trusted value from the query makes
the guarantee explicit at the call site and immune to refactoring
drift. Same change in the dry-run preview shape.
Swedish-compliance wording: the credit-note rejection comment now
spells out BOTH ML 17 kap 22–23§ requirements — distinct kreditfaktura
series AND explicit back-reference to the original invoice's
löpnummer — so any future v1 path that does support credit-note send
starts from a complete spec.
Company-settings select: kept select('*') with an explanatory comment
rather than enumerating columns. The InvoicePDF template consumes the
full CompanySettings shape; a partial allow-list risks silently breaking
rendering, and the table has no sensitive columns today (API tokens,
billing data live in scoped tables). Documents the trade-off so the
next reviewer doesn't re-litigate.
Deliberately not changed:
- Math.round → Math.trunc on VAT öre: CLAUDE.md mandates Math.round
project-wide; unilateral deviation here would diverge from the
bookkeeping engine and POST /invoices.
- 207 Multi-Status on partial post-email failures: gnubok's convention
is warnings[] in the 200 envelope; a per-route status divergence
would break the response contract clients rely on.
- Wrapper membership double-check (OWASP V8.2.1 send route): the
withApiV1 wrapper sets ctx.companyId from the URL after the
membership check — recurring false positive in this swarm.
Tests + build + lint clean. 17/17 in the touched suites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
27969c5260 | fix(dashboard): enable salary features with "Beta" badge for testing (#425) | ||
|
|
f3a3d07ed3 |
feat: one-click company setup from BankID directorships (#309)
* feat: one-click company setup from BankID directorships After BankID auth, surface Bolagsverket companies where the user is a director and provision a fully-configured gnubok company with one click instead of walking the 4-step wizard. Also exposed via CompanySwitcher's "Lägg till företag" for returning users. - New /select-company route merges gnubok memberships with TIC CompanyRoles; cards flag already-registered org numbers. - createCompanyFromTicRole server action derives entity_type, f-skatt, VAT, moms_period, and SPAR address defaults, then delegates to createCompanyFromOnboarding for consistent provisioning. - TIC /bankid/complete now requests enrichment on login too, so returning users see fresh CompanyRoles in the picker. - Middleware routes zero-membership users to /select-company when enrichment is available, /onboarding otherwise. - Inline enrichment picker removed from WelcomeOnboarding (wizard is now the manual fallback); SPAR address pre-fill preserved. - Unit tests for mapEntityType helper and createCompanyFromTicRole defaults (VAT-AB, non-VAT EF, unmappable, unauth). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address PR review feedback on BankID company picker Greptile P1 + swedish-compliance bot findings: - Move enrichment row cleanup out of createCompanyFromOnboarding and into createCompanyFromTicRole. The manual wizard also goes through createCompanyFromOnboarding, and was wiping the enrichment row before the returning-user "Lägg till företag" flow could use it. - Refuse to provision when TIC /lookup is missing. Silently defaulting vat_registered to false for a momsregistrerat bolag would create a company that issues invoices without moms (ML 17 kap violation). The picker now routes to the manual wizard with org_number pre-filled when the lookup fails, so the user confirms VAT/F-skatt manually. - Default accounting_method by entity type: enskild firma → cash (K1/kontantmetoden per BFNAR 2013:2), aktiebolag → accrual (K2/K3). - Document that moms_period='quarterly' is a provisional middle-tier default; Skatteverket's assigned period depends on turnover and the user can correct it in /settings/tax. - Fix the misleading "re-fetch from BankID" comment — /select-company only reads the cached enrichment row; it's refreshed only on the next BankID auth. - Extend test coverage: lookup-missing refusal, EF kontantmetoden default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: tighten entity-type mapping and clarify K1 threshold Second round of PR review fixes (swedish-compliance bot): - mapEntityType now uses explicit allow-lists instead of substring matches. "Enskild stiftelse" / "Enskild näringsverksamhet utan firma" no longer false-match as enskild_firma (would have provisioned with K1/kontantmetoden — ML/BFL risk). Regression guard test added. - Publikt aktiebolag explicitly included (same K2/K3 regime as private AB); Bankaktiebolag / Försäkringsaktiebolag excluded (FFFS regime). - Remove misleading claim that onboarding UI flags moms_period as provisional — no such UI exists by design (approved one-click UX). - Expand accounting_method comment to cite the 3 MSEK K1→K3 threshold (BFNAR 2013:2 vs 2017:3) so the EF→cash default is honest about its scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
11621bb79f |
Feat/skv integration full (#284)
* feat: add script to import Skatteverket monthly tax tables as fallback TypeScript module
- Implemented a new script `import-tax-tables.ts` to parse fixed-width TXT tax tables from Skatteverket (SKV 434).
- The script generates a TypeScript module for emergency fallback when the Skatteverket open-data API is unavailable.
- Supports command-line argument for specifying the year and handles parsing of B-rows only.
- Outputs a structured TypeScript file containing tax data for specified years.
* feat: gate salary module behind dev-only flag
Temporarily disable the Lön module in production while the feature is
being completed. Sidebar entries ("Löner", "Anställda") still render but
are not clickable and show a "Kommer snart" badge. Middleware redirects
/salary* to / and returns 404 on /api/salary/* so the feature can't be
reached by direct URL. All gates check NODE_ENV === 'development' so
local dev keeps full access for continued development.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: refactor bank file import wizard to streamline column mapping and enhance CSV handling
* fix: bump migration timestamp to avoid collision with logos_bucket
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: enhance AGI generation and salary entry calculations with improved status checks and error handling
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a1a816b4a5 |
Delete features (#218)
* Implement company and account deletion features - Add event types for company and account deletion to CoreEvent. - Enhance Supabase middleware to handle company context resolution and cookie management for archived companies. - Create API routes for deleting accounts and companies, including necessary validations and event emissions. - Implement tests for account and company deletion endpoints to ensure proper functionality and error handling. - Add retention notice component to inform users about bookkeeping data retention during destructive actions. - Create database migrations to support soft deletion of companies and anonymization of user accounts, ensuring compliance with retention laws. * feat: enhance account deletion process and update user notifications * Add service client for onboarding completion check and update escape hatch visibility * Enhance invite flow and email handling for company members * Refactor company context and RLS policies for active company isolation - Update `switchCompany` to remove unnecessary revalidation as client handles navigation. - Revise `getActiveCompanyId` to prioritize `user_preferences` and validate against non-archived memberships. - Modify `setActiveCompany` to ensure `user_preferences` is the authoritative source while maintaining cookie compatibility. - Enhance middleware to resolve active company using `user_preferences` and fallback to first non-archived membership. - Introduce new API route `/api/company/current` to fetch the active company ID for cross-tab synchronization. - Implement `CompanyTabSync` component for real-time active company enforcement across tabs. - Create migration for RLS policies to enforce single-active-company isolation using `current_active_company_id()`. * feat: implement viewer role enforcement for write permissions - Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company. - Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions. - Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers. - Created tests to verify the behavior of the viewer role and write permissions. - Added database migration to enforce read-only access for viewers at the database level. |
||
|
|
bf5a8d9195 |
Fix/multiple company (#203)
* Refactor onboarding and dashboard logic; add silent team creation for users - Removed unnecessary useCompany context in DashboardContent and SettingsSidebar components. - Simplified onboarding setup logic to allow direct access to the dashboard for users without companies. - Introduced WelcomeOnboarding component to handle user onboarding steps. - Added migration to create silent teams for all users at signup, backfilling existing users without teams, and cleaning up incomplete companies. * fix: update greeting logic and improve email handling in TIC extension * Redirect to onboarding for users without companies and update onboarding flow * Build issue fix * Enhance onboarding experience by adding existing companies check |
||
|
|
6d75b9a1bf |
feat: BankID authentication via TIC Identity API (#192)
* feat: add BankID authentication via TIC Identity API Integrate BankID as a login/signup method using the TIC Identity API. Users can authenticate with BankID QR codes (desktop) or deep links (mobile), link BankID to existing accounts, and skip TOTP MFA when BankID is linked. Removes Step 0 (role choice) from onboarding for all users. Adds enrichment data support for pre-filling company details from Bolagsverket during signup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — server-side rate limit, unlink clears MFA bypass - Add per-IP rate limit (5s cooldown) on /bankid/start to prevent unbounded billable TIC sessions from unauthenticated callers - Add /bankid/unlink endpoint that deletes bankid_identities AND clears app_metadata.bankid_linked so MFA enforcement resumes after unlink - Update BankIdSettings to call server-side unlink instead of client-side delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move rate limiter to module scope, add BankID logo and year-end skill Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
0dd1f5ebc1 |
feat: multi-tenant company refactor (GNU-19) (#153)
* feat: multi-tenant company refactor (GNU-19) Introduce companies table, company_members, and user_preferences to support multiple companies per user. All data scoping changes from user_id to company_id across the entire codebase. Key changes: - Database migration: new tables, company_id on 40+ tables, backfill, RLS rewrite from user_id to company-member-based, updated RPCs - Types: Company, CompanyMember, CompanyRole, UserPreferences types; company_id added to all entity interfaces; companyId on all events - Engine: all 7 core functions take companyId; storno, period, year-end services updated; 16 report generators updated - Middleware: company context resolution (cookie → prefs → first company) - API routes: ~120 routes updated with requireCompanyId() - Frontend: CompanyProvider context, layout/dashboard/onboarding updated - Extensions: context factory, 9 extensions, all lib files updated - Tests: 1880 tests passing, all helpers updated with company_id defaults Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add database migrations for multi-tenant company and team system (GNU-19) Adds company_invitations, company creation RPC, team_members, account deletion RPC, and teams table refactor migrations. Updates base multi-tenant migration with cascading FKs and onboarding_step column. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add team types and update core infrastructure for multi-tenancy (GNU-19) Adds TeamRole, MemberSource, and Team types. Refactors Supabase service client to be stateless, updates middleware for team-aware routing, extends CompanyContext with team/role fields, and updates extension service types to accept companyId. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: thread company_id through business logic functions (GNU-19) Replaces user_id scoping with company_id across all lib modules: bookkeeping, documents, transactions, invoices, reconciliation, tax, deadlines, and import. Updates corresponding tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: thread company_id through API routes and extensions (GNU-19) Updates all existing API routes to extract and pass companyId. Updates enable-banking and arcim-migration extensions for company-scoped transaction ingestion and sync. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add company and team management API routes (GNU-19) Adds CRUD endpoints for company members, company invitations, team members, and team invitations. Includes invite token utilities, email templates, and company switch server action. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add team/company UI components, pages, and dashboard updates (GNU-19) Adds CompanySwitcher, ConsultantEmptyState, Step0RoleChoice, company members and team management panels. Updates dashboard layout for team-aware routing, onboarding for multi-step role choice, and auth callback for team invite acceptance. Ignores supabase/.branches/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add null guards for company in import page (GNU-19) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move appUrl declaration to outer scope in invite route (GNU-19) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add optional chaining for company.name in members section (GNU-19) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add optional chaining for second company.name in members section (GNU-19) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add null guards for company in extension components (GNU-19) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: pass companyId to executeSIEImport in arcim-migration extension (GNU-19) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update tests to use companyId instead of userId and improve type handling --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
109f860e22 |
Sandbox (#8)
* feat: add sandbox infrastructure — migration, types, and middleware Add database migration for sandbox support: - Add `is_sandbox` boolean column to company_settings - Update 4 enforcement trigger functions (journal entry immutability, journal entry line immutability, retention enforcement, document deletion blocking) to bypass checks for sandbox users - Add `cleanup_sandbox_user()` SECURITY DEFINER function that handles FK-safe deletion order (document_attachments → journal_entry_lines → journal_entries → supplier_invoices → auth.users cascade) - Add `cleanup_expired_sandbox_users()` function that loops over sandbox users older than N hours with per-user error handling Update TypeScript types: - Add `is_sandbox: boolean` to CompanySettings interface - Add `is_sandbox: false` to makeCompanySettings() test factory Update middleware: - Add `/sandbox` to public routes so the landing page is accessible without authentication Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add sandbox landing page, seed API, cleanup cron, and banner Sandbox landing page (app/sandbox/page.tsx): - Client component matching the existing auth page aesthetic - Auth check: if logged in as real user, shows message to use incognito - Otherwise shows feature overview (invoices, transactions, bookkeeping, reports) with "Starta sandbox" button - On click: signInAnonymously() → POST /api/sandbox/seed → redirect - Uses window.location.href for full page load (ensures middleware picks up new session cookies) Seed API (app/api/sandbox/seed/route.ts): - POST handler gated to anonymous users only (403 for real users) - Idempotent: returns { seeded: false } if company_settings exists - Seeds ~40 rows: profile, company_settings (is_sandbox: true, onboarding_complete: true), chart of accounts (via RPC), fiscal period, 3 customers (Swedish business, EU business, individual), 4 invoices (paid/sent/overdue/draft), 4 invoice items, 2 posted journal entries with 5 lines, 8 transactions (3 categorized, 2 income, 3 uncategorized), 2 deadlines - Journal entries inserted directly (not via engine) to avoid event emission, using next_voucher_number() RPC Cleanup cron (app/api/sandbox/cleanup/cron/route.ts): - GET handler with CRON_SECRET Bearer token auth - Creates service role Supabase client - Calls cleanup_expired_sandbox_users RPC (24h default) Sandbox banner (components/dashboard/SandboxBanner.tsx): - Amber bar with dismiss button (client state, reappears on reload) - Text: "Sandlådemiljö — dina data raderas automatiskt efter 24 timmar" - "Skapa konto" link to /register Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate sandbox into dashboard — banner, nav, settings safeguards Dashboard layout (app/(dashboard)/layout.tsx): - Fetch is_sandbox from company_settings - Render SandboxBanner at top of page for sandbox users - Pass isSandbox prop to DashboardNav - Hide RecaptIdentify analytics for sandbox users Root page (app/page.tsx): - Same sandbox banner and isSandbox prop treatment as dashboard layout (root page has its own layout, not wrapped by (dashboard)/layout) DashboardNav (components/dashboard/DashboardNav.tsx): - Add optional isSandbox prop - Change logout button text to "Avsluta sandbox" when isSandbox - Redirect to /sandbox instead of /login on logout for sandbox users - Applied to both desktop sidebar and mobile drawer logout buttons Settings page (app/(dashboard)/settings/page.tsx): - Hide "Bank (PSD2)" tab entirely for sandbox users — prevents connecting real bank accounts from a temporary anonymous session - Hide "Radera konto" card for sandbox users — account auto-deletes via cron, and the delete flow requires email confirmation Vercel config (vercel.json): - Add sandbox cleanup cron at 04:00 UTC daily (/api/sandbox/cleanup/cron) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove audit trigger for non-existent tax_codes table Migration 018 referenced public.tax_codes which was never created (migration 012 is a placeholder). This caused failures when running migrations from scratch on a fresh database. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove ALTER FUNCTION for 3 non-existent functions Removed search_path pinning for create_invoice_with_items, seed_asset_categories, and update_reconciliation_session_counts — none of these functions were ever created in any migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove ALTER for generate_invoice_number (created in later migration) The function is created in migration 20260306 with search_path already set, but migration 20260304 tried to ALTER it before it existed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fixed redirect issue * Update app/api/sandbox/seed/route.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update app/api/sandbox/seed/route.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update app/sandbox/page.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Fixed catch block issue --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> |
||
|
|
928a145f9a |
feat: upgrade auth to email+password with optional TOTP MFA
- Replace magic-link-only login with email+password (primary) and magic link (toggle) - Add registration page with strong password validation - Add MFA enrollment (/mfa/enroll) with QR code and manual secret - Add MFA verification (/mfa/verify) with 6-digit TOTP input - Add password reset flow (/reset-password) - Add middleware MFA enforcement gated by NEXT_PUBLIC_REQUIRE_MFA env var - Self-hosted deployments (NEXT_PUBLIC_SELF_HOSTED=true) skip MFA entirely - Add Security tab in Settings for password change and MFA management - Add requireAuth() API route helper with MFA check - Update CLAUDE.md with Authentication section and env var docs - Update Dockerfile and docker-entrypoint.sh for new env var placeholders Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
31dec292fe |
fix: CSP blocking hydration and Enable Banking widget, stale auth cleanup
- Add 'unsafe-inline' to script-src so Next.js hydration scripts run - Whitelist *.enablebanking.com in CSP (script, style, connect, img) - Allow HTTPS images broadly for third-party bank logos - Clear stale refresh tokens in middleware (skip on /auth callback) - Fix login button disabled on browser autofill by reading email from form DOM Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
0aecf5b41c |
Refactor to generic ERP base
Remove influencer-specific features (campaigns, TikTok, gifts, shadow ledger, contracts, briefings) and consolidate into a clean ERP foundation with core bookkeeping, invoicing, receipts, tax reporting, and calendar functionality. Reorganize database migrations into a clean numbered sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
a25d10a528 | Initial copy from influencer-biz |