POST /api/banking/plaid/webhooks was @PublicRoute() and processed the
body without verifying Plaid's Plaid-Verification JWT, letting any
unauthenticated client replay or fabricate webhook events for a tenant
by guessing a plaidItemId.
Add PlaidWebhookVerificationService that verifies the Plaid-Verification
ES256 JWS using a JWK fetched from plaidClient.webhookVerificationKeyGet
(cached per kid via lru-cache for 24h), enforces a 5-minute iat replay
window through jose.jwtVerify({ maxTokenAge }), and timing-safe compares
the body's SHA-256 against the request_body_sha256 claim. The webhook
controller now consumes the raw body and the plaid-verification header,
runs verification before setupPlaidTenant, and returns 400 Bad Request
on any failure - so no tenant context is ever set for an unsigned or
tampered request.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolve a CLS middleware in App.module.ts to copy the request
`organization-id` header straight into `cls.organizationId`, which the
TenancyDB factory used to pick the per-tenant database. The JWT path
never set `organizationId` from the authenticated user, and
TenancyGlobalGuard only checked that the header was present — so any
authenticated user could read or write another tenant's database by
sending their own JWT plus the victim's `organization-id`.
Make the JWT-resolved tenant the source of truth and validate the
header at the edge:
- AuthSigninService.verifyPayload now loads the user's tenant and sets
`cls.organizationId` from `tenant.organizationId`, mirroring the
API-key path in AuthApiKeyAuthorizeService.
- TenancyGlobalGuard rejects with `Organization mismatch.` when the
request header disagrees with the CLS value set by the auth guard.
- App.module.ts no longer seeds `cls.organizationId` from the
attacker-controlled request header.
GHSA-2g96-86rw-qmvg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add tenant-scoped document lookup with throwIfNotFound() before S3
operations in GetAttachment, DeleteAttachment, and
GetAttachmentPresignedUrl services. This prevents users from reading,
deleting, or generating presigned URLs for attachments belonging to
other tenants.
Also adds RequirePermission decorators to the three attachment
endpoints and introduces Attachment ability subject with View and
Delete actions.
GHSA-rc4v-wq22-v6cf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The balance sheet rendered "Current Liabilties" in the Liabilities
section because of a typo in the i18n key, the schema reference, the
swagger example responses, and the generated SDK fixtures. Fixed all
five locations so PDF/HTML/JSON renders all read "Current Liabilities".
- packages/server/src/i18n/en/balance_sheet.json: rename key and value
- packages/server/src/modules/FinancialStatements/modules/BalanceSheet/
BalanceSheetSchema.ts: update i18n key reference
- packages/server/src/modules/FinancialStatements/modules/BalanceSheet/
BalanceSheet.swagger.ts: fix three example response strings
- shared/sdk-ts/openapi.json + schema.ts: regenerate to match
Hoist `page` and `pageSize` declarations from the per-module DTOs into
the shared DynamicFilterQueryDto base class. Without these declarations,
the global ValidationPipe (whitelist: true) strips the params from the
request before the service layer sees them, so list services fall back
to their default page=1, pageSize=12 regardless of what the client sent.
Affects 10 collection GET endpoints whose query DTOs are empty subclasses
of DynamicFilterQueryDto: expenses, bills, credit-notes, manual-journals,
payments-received, sale-invoices, sale-estimates, sale-receipts,
vendor-credits, item-categories.
The 3 already-working DTOs (Customers, Vendors, Items) keep their local
page/pageSize declarations as redundant overrides — no behavior change.
Closes#1088
Export column headers displayed raw i18n keys like
`expense.field.payment_account` instead of translated names like
"Payment Account" because ExportResourceService never resolved the
i18n keys before rendering.
Inject I18nService and translate column names in both
getExportableColumns() (CSV/XLSX) and getPrintableColumns() (PDF).
Closes#1073
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reports (Receivable Aging Summary, Payable Aging Summary, Inventory
Valuation, Sales Tax Liability Summary, Vendor Balance Summary) were
not assigning baseCurrency from meta in their constructors, causing
currency formatting to fall back to USD instead of the organization's
base currency.
Closes#1069
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GET /api/attachments/:id crashes with "Cannot read properties of
undefined (reading extension)" when the S3 object has no ContentType
metadata. This happens when files are uploaded without explicit content
type (e.g., via API integrations).
mime.extension(undefined) returns undefined, which then causes the
Content-Disposition header template to fail.
Fix: fallback to "application/octet-stream" when ContentType is missing,
and "bin" when mime.extension() returns undefined.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Prettier scripts to webapp package for code formatting, consistent
with the existing server package setup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bulk uncategorize transactions - fix API endpoint and error handling
* refactor: use params object instead of URLSearchParams for delete request
Simplifies the API call by passing params object directly to the delete
method instead of manually building URLSearchParams.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Ahmed Bouhuolia <a.bouhuolia@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The NestJS app requires database and Redis connections to bootstrap.
Added GitHub Actions services for MySQL and Redis with necessary
environment variables for the openapi:export command to work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The server depends on @bigcapital/email-components and other shared
packages. Build them before running openapi:export to fix module
resolution errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds workflow that triggers on server code changes to:
- Export OpenAPI spec from NestJS Swagger module
- Generate TypeScript types with openapi-typescript
- Build the SDK package
- Create PR with changes if any exist
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>