d4ef4f8bc4cbecb3cbdf1cfde064cf2e87df2949
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fbd4b992f5 |
Add/db and speed (#1243)
* fix(privacy): make privacy policy page dark mode friendly Replace the hardcoded light gradient background with bg-background and add dark:prose-invert to the prose blocks so body text is readable on dark cards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(cloud-backup): sync archives to Dropbox alongside Google Drive Introduce a CloudStorageProvider interface so performSync builds the archive set once and talks to storage only through it. Google Drive keeps its existing behaviour; Dropbox is a second implementation, so the compliance-relevant half (fingerprints, per-year layout, size fallback, progressive persistence) cannot drift between targets. Dropbox uses App folder access, matching the drive.file scope's "only what the app created" guarantee. Uploads are single-shot under 8 MB and chunked upload sessions above, every write verified against Dropbox's content_hash. Call arguments are ASCII-escaped per UTF-16 code unit so Swedish file names survive the Dropbox-API-Arg header. Each provider owns its extension_data keys, schedule, failure counter and alert throttle, so a dead Dropbox token cannot pause a healthy Drive backup. The google_drive_* keys and the /oauth/callback path are untouched: both are wire format for already-connected companies. isConfigured() gates /connect only. A deployment that loses its OAuth credentials must not trap users with a connection they cannot remove or a schedule they cannot switch off. Requires DROPBOX_APP_KEY and DROPBOX_APP_SECRET; the provider row renders disabled without them. No migration: state is extension_data JSON throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: remove merge-conflict markers committed in DECISIONS.md The merge that brought main into this branch staged DECISIONS.md while it still carried conflict markers, so cdc3a513 shipped an unresolved hunk (compliance swarm ISO 27001 A.8.32). DECISIONS.md is an append-only log, so both sides are kept: main's systemdokumentation entry followed by this branch's Dropbox entries. No decision was dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2be104ba34 |
fix(cloud-backup): mark dead Google tokens needs-reauth and surface reconnect in the UI (#970)
Nightly cloud-backup syncs kept retrying Google connections whose refresh token is permanently dead (Google returns 400 invalid_grant; 3 of 12 prod connections are in this state), and the settings card showed the raw English error string while presenting the account as connected. - refreshAccessToken now throws a typed GoogleTokenRefreshError carrying status + body, with an isInvalidGrant discriminator. - performSync catches the invalid_grant case, persists status: 'needs_reauth' (+ needs_reauth_at) on the connection JSON in extension_data (no migration needed), and returns a needs_reauth failure instead of throwing. Transient failures (5xx, network, other 400s) still throw and stay retried. - The nightly cron loads connections for due companies and skips needs_reauth ones (reported as skipped in the summary) instead of retrying the dead token every night. A successful refresh clears a stale flag; reconnecting via OAuth writes a fresh connection. - CloudBackupCard shows a reconnect callout (Swedish-first, sv+en strings) wired to the existing connect action, and replaces the raw error string on the schedule row with a short reconnect notice. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ec27228a8e |
style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
02f94ef631 |
Fix/critical issues (#351)
* fix: add 15s timeout to accounting provider HTTP clients Node's built-in fetch has no default timeout, so a stalled provider could hold a serverless worker open for many minutes — worse with withRetry (6x on Fortnox, 3x on others) and getPaginated stacking across pages. Wrap each fetch() in the Fortnox, Visma, Bokio, Briox, and Björn Lundén clients with signal: AbortSignal.timeout(15_000), and treat TimeoutError/AbortError as retryable so a single stalled attempt retries cleanly instead of hanging the request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: add timeouts to OAuth token endpoints Wrap every OAuth2 token exchange, refresh, and revoke POST in an AbortController via a new fetchWithTimeout helper. Without this, a hung provider endpoint holds the request thread indefinitely — worst case being Skatteverket, where refreshAccessToken sits on the hot path of every bookkeeping action and exchangeCodeForTokens races the 5-minute BankID auth-code TTL. On timeout, the Skatteverket OAuth callback now redirects to /reports?tab=vat-declaration with a Swedish retry message instead of leaving the user stranded on the callback URL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: close RLS escalation on membership and settings tables Any authenticated user who was a member (including viewer) could issue a direct PostgREST PATCH against company_members and promote themselves to owner, bypassing the app-layer requireWritePermission guard entirely. Reproduced on prod, then verified the fix on staging. Tighten INSERT/UPDATE/DELETE policies on company_members, team_members, api_keys, company_invitations, team_invitations, companies, teams, and company_settings to require the caller to hold role IN ('owner','admin') in the target company/team. Role check is wrapped in SECURITY DEFINER helpers (user_is_company_admin, user_is_team_admin, user_role_in_company) to avoid RLS recursion when a policy on company_members references company_members in its subquery. Add a BEFORE UPDATE trigger on company_members that rejects any role change unless the caller already holds role='owner', so admins cannot mint further owners even though they can otherwise write. Legitimate write paths are unaffected: company creation goes through the create_company_with_owner SECURITY DEFINER RPC, invite acceptance uses the service role, and team->company membership syncs via SECURITY DEFINER triggers. All bypass RLS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(migrations): resolve duplicate schema_migrations version 20260421160000 Two migration files shared timestamp 20260421160000 on main (booking_template_usage.sql and opening_balances_rpc.sql), causing supabase_migrations.schema_migrations PK collisions on any fresh CI run: duplicate key value violates unique constraint "schema_migrations_pkey" Key (version)=(20260421160000) already exists. Bump opening_balances_rpc.sql to 20260421160500. booking_template_usage keeps 20260421160000 because its table already exists on prod; the renamed file has an idempotent CREATE OR REPLACE FUNCTION body and has not yet been deployed to prod, so moving its version is free. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(migrations): make booking_template_usage migration idempotent The table already exists on prod (applied out-of-band) but prod's schema_migrations does not track version 20260421160000, so the next PR-driven deploy would re-run this migration and fail on `CREATE TABLE public.booking_template_usage` with a duplicate-relation error. Add IF NOT EXISTS to CREATE TABLE and CREATE INDEX, and DROP POLICY IF EXISTS before each CREATE POLICY. No functional change on fresh databases; prod just silently no-ops the table/index creates and re-declares policies without dropping-then-missing them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: implement isTimeoutError utility and enforce role restrictions on company_members insert * fix: implement fallback for user_id in commit_journal_entry function when auth.uid() is NULL --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d708a85d4c |
Feat/cloud backup (#277)
* feat: cloud backup to Google Drive + full-archive all-scope Adds a cloud-backup extension that uploads a full-company backup ZIP to the user's own Google Drive via OAuth (drive.file scope only). Refresh tokens are AES-256-GCM encrypted before being stored in extension_data. The full-archive export gains a scope=all mode for whole-company backups (per-period SIE under sie/, per-period rapporter/ subfolders, flat dokument/ manifest tagged with fiscal_period_id). An 80 MB size guard short-circuits generation before the platform response limit. Also fixes a latent bug in lib/core/audit/audit-service.ts where the parameter was named userId while the query filtered by company_id; the audit-trail API route was passing user.id so audit queries returned empty unless user and company shared a UUID. Drive-by: scope the dashboard "fresh start" localStorage key per companyId so dismissing the setup checklist in one company no longer carries over to others. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address review comments on cloud backup + archive export - Extend audit trail to_date to end-of-day so last-day entries aren't silently excluded from period-scoped archives. - Apply 413 size-limit guard regardless of include_documents, using the overhead-only figure when documents are excluded. - Use crypto.randomUUID() for Drive multipart boundary to eliminate any collision risk with ZIP payload bytes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: migrate legacy setup-gate localStorage keys on dashboard Users who previously dismissed the setup checklist via the old global erp_setup_fresh_start or erp_checklist_dismissed keys were re-gated after the switch to a company-scoped key. Fall back to the legacy keys on read and migrate them to the scoped key on first hit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: update customer email handling and anonymization rules in supportmail-to-ticket skill * test: update audit trail to_date expectation for end-of-day timestamp Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |