Validate sortOrder against an allowlist at the DTO layer, normalize the
direction centrally in DynamicFilterSortBy.buildQuery, and re-sanitize
inside every orderByRaw modifier so attacker-controlled SQL cannot reach
the ORDER BY clause.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The multer-s3 storage factory used `Date.now().toString()` as the S3 key
for every upload, yielding a 13-digit ms-epoch key. The keyspace for any
time window equals the millisecond count of that window, so an attacker
holding a registered account can enumerate keys for known upload moments
(e.g. ~10 minutes for a 10-second window with a 10-proxy rotation), then
download files via `GET /attachments/:id/presigned-url`. Two uploads in
the same millisecond also collide and silently overwrite each other.
Replace the key callback with `${organizationId}/${randomUUID()}`:
* `randomUUID()` from `node:crypto` is a v4 UUID with 122 bits of
entropy, making brute-force enumeration infeasible.
* The `<organizationId>/` prefix (read from the `nestjs-cls` store
populated by `ClsModule` middleware in `App.module.ts`) limits the
blast radius of any hypothetical bucket-listing leak to a single
tenant.
Add a tenant migration applying `unique` to `documents.key` so any future
key collision surfaces as a DB error instead of a silent S3 overwrite.
Legacy 13-digit numeric keys remain accessible via their stored values;
only new uploads use the new format.
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>
- Introduced a transaction for creating tenants, linking users, and saving metadata in the `CreateWorkspaceService`.
- Moved tenant marking as building to the `markAsBuilding` method for better separation of concerns.
- Added `WorkspaceBuildJobResponseDto` for improved API response structure.
- Updated `GetWorkspacesService` to utilize a transformer for cleaner data mapping.
- Added unit tests for `CreateWorkspaceService` to ensure robust functionality and error handling.
- Add `user_tenants` system DB migration for many-to-many user-to-org relationship
- Add backfill migration to populate existing users into join table
- Add `UserTenant` Objection.js system model and register globally
- Enforce org membership validation in `TenancyGlobalGuard` (security)
- Add `modules/ee/Workspaces` with full CRUD: create, list, delete, build-status
- Add `CreateUserTenantOnSignupSubscriber` for backward-compatible signup flow
- Register `WorkspacesModule` in `AppModule`
API endpoints:
GET /workspaces - list all orgs user belongs to
POST /workspaces - create new org (async build)
GET /workspaces/build/:jobId - poll build job status
DELETE /workspaces/:orgId - delete org (owner only)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated Dockerfiles for server and webapp to copy all shared packages in a single command, improving maintainability.
- Removed individual package copy commands for bigcapital-utils, pdf-templates, and email-components.
- Ensured that all shared packages are included automatically during the build process.
Add missing viewSlug, filterRoles, stringifiedFilterRoles, searchKeyword,
columnSortBy, sortOrder, customViewId, page, and pageSize properties to
GetAccountsQueryDto to enable proper filtering when selecting table views
(Assets, Liabilities, Equity, Income, Expenses) on the Accounts Chart page.
Previously, the API received view_slug but didn't process it because the
DTO lacked these properties, causing all accounts to be returned instead
of filtering by the view's root_type.
Fixes#1023
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change @IsInt() to @IsNumber() for paymentAmount field in PaymentReceivedEntryDto
to allow recording payments with cents (e.g., $1,679.80).
Fixes#1016
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Introduced new DTOs for various financial reports including Balance Sheet, Cash Flow Statement, and Aging Summaries.
- Updated existing controllers to utilize the new DTOs and enhance OpenAPI documentation with proper schema references.
- Removed unnecessary query parameters from the Bank Accounts controller.
- Enhanced response structures for better data representation in reports.
Add missing function invocation on LinkModel to properly call query method.
The model reference was missing parentheses to invoke the factory function.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generate presigned URL for companyLogoKey when retrieving PDF template
to allow the logo to display when reopening the branding template drawer.
- Inject GetAttachmentPresignedUrl service in GetPdfTemplateService
- Generate companyLogoUri from companyLogoKey in template attributes
- Add companyLogoUri to transformer included attributes
Fixes the issue where logo uploads and saves but doesn't show when
reopening the branding template customization drawer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixed attachments not showing in edit forms for various transaction types by:
1. Adding @InjectAttachable() decorator to models:
- SaleReceipt, SaleEstimate, CreditNote, PaymentReceived
- Bill, BillPayment, VendorCredit
- ManualJournal, Expense
2. Fixing transformers to include attachments in API responses:
- SaleReceiptTransformer, PaymentReceivedTransformer
3. Registering missing event subscribers in Attachment.module.ts:
- AttachmentsOnSaleReceipts, AttachmentsOnSaleEstimates
4. Fixing DocumentLink model relation mapping:
- Changed Document.default to Document for proper module export
5. Fixing PaymentReceived model_ref consistency:
- Changed from 'PaymentReceive' to 'PaymentReceived' to match class name
6. Adding missing withGraphFetched('attachments') to GetPaymentReceived.service.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>