github vendurehq/vendure v3.7.4

3 hours ago

Vendure v3.7.4 is a patch release containing fixes for two reported vulnerabilities (one high, one low), clearer reporting from vendure migrate, and a batch of core, dashboard and job queue fixes.

If you have written a subclass of BaseStockLocationStrategy, a custom StockAllocationStrategy, or you delegate API key management to roles other than SuperAdmin, read Behaviour Changes.

Security

Fixes for the following reported vulnerabilities are in 3.7.4. If you are on an earlier 3.x version, upgrade.

Severity Vulnerability Advisory
High API key privilege escalation: an administrator holding only UpdateApiKey could rotate an API key with higher privileges than their own and receive its new plaintext secret, taking over a live credential. createApiKey, updateApiKey and deleteApiKeys did not check the caller's permissions against the key's roles either, and API key reads were not scoped to the keys the caller may manage GHSA-37xp-mjp8-6f9x
Low Stock overselling under concurrent checkout: stock levels were updated with an unlocked read-modify-write, and the saleable stock check ran separately from allocation. Concurrent checkouts for the same variant could sell more units than were in stock, and stockAllocated and stockOnHand drifted from the real counts GHSA-8ghm-q833-cmgp

Our thanks to the researchers who reported these responsibly.

Core

  • Saving an Order no longer detaches its surcharges (#5164). A blocking OrderLineEvent handler could replace a surcharge while the Order was being saved. TypeORM then set orderId to NULL on surcharges which were still in use.
  • A Promotion whose actions discount nothing is no longer attached to the Order, so it no longer uses up usageLimit or perCustomerUsageLimit (#5279, closes #5278). Promotions with side effects, such as the documented free gift example, are still kept.
  • Plugin configuration functions can now push custom fields onto a plugin's own entities (#5455, closes #3274). This previously threw Cannot read properties of undefined (reading 'push').
  • localeText custom fields can be sorted by (#5265). The CSV importer accepts translation columns for them, and config validation rejects them on entities which are not translatable (#5349, closes #5328).
  • Validate custom fields passed to registerCustomerAccount (#5180).
  • Correct productVariantCount on nested Collection children (#5478).
  • Correct the inverse side of the CustomerGroup.taxRates relation, which returned the tax rates whose zone id matched the group id (#5176).
  • Apply relevance ordering when the search sort input is empty (#5283).
  • De-duplicate variant ids from an OR-ed variant name filter (#5416).
  • Stop EntityHydrator hanging when re-merging a ShippingMethod (#5429).
  • Job queue: InMemoryJobQueueStrategy returns copies of jobs from findOne() and findMany(), so SubscribableJob.updates() no longer stops after the first update (#5293). InMemoryJobQueueStrategy also keys its store by string id (#5452) and counts totalItems before pagination (#5394). A job cancelled while running stays cancelled when its process returns (#5444). Failed scheduled tasks log the stack trace and error class (#5277).
  • Export SubscribableJob and the documented entities from the package barrel (#5275).

Performance

  • Batch stockLevels lookups per request (#5360).
  • Fix the slow move-collections dialog on deeply nested Collection trees (#5399).

Dashboard

  • Add a customer email column to the order list (#5443).
  • Refresh the eligible shipping methods after a draft order changes (#5261, closes #5253).
  • Keep column filters AND-ed with the list search term (#5273).
  • Use moneyStrategyPrecision for money input display, blur and step (#5453).
  • Keep create forms valid when an entity has non-nullable custom fields (#5242).
  • Resolve Country custom fields from the Region config (#5449).
  • Fix country selection in address forms (#5267).
  • Allow typing in dialogs opened from an open bulk actions menu (#5397).
  • Build the router once per page load (#5459).
  • Orders pre-loaded from the list use the extended order detail query, so fields added by an extension load on the order detail page (#5173).
  • Warn when editing a shared option group (#5364), when an extension route collides with an existing route (#5365), and when a package.json import is skipped (#5329).
  • A display component registered with addDisplayComponent renders in list columns again, including columns with a core cell function (#5339) and additionalColumns (#5366).
  • Extension compilation: include imported JSON in the config compilation (#4808) and add JSON import attributes in ESM mode (#5361).
  • Translations: interpolation and plurals work in dashboard extensions (#5131), order line field names are translated (#5464), the "State" label means condition across locales (#5302), and the "orderState." prefix is removed from the French translations (#5387).
  • Label localized custom fields with the field name rather than the form path (for example tagline rather than translations.0.customFields.tagline) when no label is configured (#5313, closes #5246).

CLI

  • vendure migrate used to report "No pending migrations found" in two cases where it should not: the configured migrations patterns match no files, or the database schema is out of sync with the entities. vendure migrate now reports both cases (#5174, closes #5001). The exit code is unchanged.
  • A TypeScript compile error in the server or worker no longer ends the whole vendure dev run (#5323). The new DevOptions.reloadIgnorePaths lets a CLI plugin exclude generated paths from triggering a restart.

@vendure/create

  • Scaffolded projects use the real storefront port in vendure-config (#5338) and point email URLs at the starters' real routes (#5401).
  • Exit when the Docker fallback prompt is cancelled (#5405).

Plugins

  • Asset Server Plugin: use forward slashes in asset identifiers on Windows (#5403).
  • Email Plugin: rebuild the nodemailer transport when its options cannot be compared as JSON, for example an SES client with circular references. The plugin previously kept using the stale transport (#5412).
  • Job Queue Plugin: startup indexing reads job ids and metadata in batches of 100 instead of deserializing every retained payload, which could exhaust the worker heap (#5297).
  • Testing: avoid races creating the SQL.js data directory (#5363).

Additions

  • StockShortfallEvent is published when stock allocation for an Order cannot be fully satisfied. Use it to refund, backorder or notify.
  • StockLevelService.lockStockLevelsForVariants(), StockLevelService.getLockedStockLevelsForVariant() and StockLevelLockOptions, for custom stock strategies which need the same row locks as core.
  • RunMigrationsOptions.onDiagnostic lets a programmatic caller of runMigrations() receive the new migration diagnostics.
  • ApiKeyService is exported from @vendure/core.

Behaviour Changes

No APIs are removed or renamed. The changes below make some operations reject callers, or allocate less stock, where 3.7.3 allowed them.

API keys. createApiKey, updateApiKey, rotateApiKey and deleteApiKeys now throw unless the caller holds every permission of the key's roles. A key the caller may not manage is reported as not found, and is left out of the API key list queries.

Stock allocation. DefaultStockLocationStrategy now caps an allocation at the available stock (stockOnHand - stockAllocated - outOfStockThreshold, summed over all stock locations). Previously it allocated the full quantity without reading stock. An Order which loses the race for the last units is now under-allocated rather than oversold, and StockShortfallEvent is published.

Stock locking. The saleable stock check and allocation now take row locks on the StockLevel rows. SQLite and SQL.js have no row locks, so they fall back to an unlocked read and log a warning once per process. StockMovementService.adjustProductVariantStock() runs in its own transaction and reads stock with a row lock. Two concurrent absolute stock updates store the later value. Previously both changes were added together.

Custom stock strategies. A subclass of BaseStockLocationStrategy which overrides init() must call super.init(injector). If it does not, it gets an InternalServerError saying that init() must call super.init(injector), instead of a TypeError. A custom StockAllocationStrategy.shouldAllocateStock() may now be called more than once per transition, so it must return the same result for the same arguments and have no side effects.

Migration

No database migrations are required. Upgrade all @vendure/* packages together to 3.7.4.

What's Changed

  • ci: add explicit permissions to workflow jobs by @michaelbromley in #5256
  • ci: skip SonarQube scan on dispatched feature branch runs by @michaelbromley in #5257
  • chore(deps): bump @typescript-eslint/eslint-plugin from 5.62.0 to 8.68.0 by @dependabot[bot] in #5240
  • chore(deps-dev): bump @commitlint/config-conventional from 19.8.1 to 21.2.2 by @dependabot[bot] in #5236
  • chore(deps): bump open from 10.2.0 to 11.0.1 by @dependabot[bot] in #5238
  • ci: build against a fresh resolve of the declared ranges weekly by @michaelbromley in #5232
  • chore(deps): bump the github-actions group across 1 directory with 10 updates by @dependabot[bot] in #5212
  • fix(core): Fix build failures against freshly resolved dependency ranges by @michaelbromley in #5299
  • fix(core): log stack trace and error class for failed scheduled tasks by @brmk in #5277
  • fix(dashboard): keep column filters AND-ed with the list search term by @brmk in #5273
  • docs(core): document that /health no longer runs health check strategies by @gabriellbui in #5301
  • docs: align commit message examples with the enforced commitlint rules by @brmk in #5290
  • fix(core): do not record promotions that apply no discount by @brmk in #5279
  • fix(core): apply relevance ordering when search sort input is empty by @brmk in #5283
  • docs: lowercase the PR title example in the localization guide by @brmk in #5320
  • fix(docs): update js-yaml to 3.15.2 to resolve security advisories by @michaelbromley in #5319
  • fix(cli): keep vendure dev resilient to a compile-error crash by @michaelbromley in #5323
  • fix(dashboard): include imported JSON in dashboard config compilation by @StefanOrzu in #4808
  • fix(dashboard): correct "State" translation across locales to mean condition by @cuyl in #5302
  • fix(core): sort by localeText custom fields by @biggamesmallworld in #5265
  • fix(dashboard): show field name instead of form path as custom field … by @Ismatulla in #5313
  • fix(core): export SubscribableJob and documented entities from the package barrel by @brmk in #5275
  • fix(core): prevent orphan-nullification of surcharges on Order save by @thomslbua in #5164
  • test(core): add e2e coverage for createFromFileStream without a RequestContext by @wakqasahmed in #5260
  • chore(deps): bump @rollup/rollup-linux-x64-gnu from 4.55.1 to 4.63.1 by @dependabot[bot] in #5307
  • fix(dashboard): extend order detail query when pre-loading order details by @LucidityDesign in #5173
  • docs: note that field resolvers only get guards and how to hook per-field logic by @michaelbromley in #5336
  • fix(dashboard): warn when a package.json import is skipped by @michaelbromley in #5329
  • chore(deps): bump the github-actions group with 4 updates by @dependabot[bot] in #5303
  • chore(deps): bump the linting group with 2 updates by @dependabot[bot] in #5304
  • chore: restrict Dependabot major updates to development tooling by @michaelbromley in #5340
  • chore(deps-dev): bump the linting group with 2 updates by @dependabot[bot] in #5341
  • fix(dashboard): restore display component precedence over core cell fn by @wakqasahmed in #5339
  • chore(deps): bump lucide-react from 0.475.0 to 0.577.0 by @dependabot[bot] in #5345
  • fix(core): validate custom fields passed to registerCustomerAccount by @MillisJn in #5180
  • fix(core): warn when migration patterns stop matching by @biggamesmallworld in #5174
  • Invalidate eligible shipping methods after draft-order changes by @wakqasahmed in #5261
  • fix(dashboard): fix country selection in address forms by @psang39 in #5267
  • fix(core): treat localeText custom fields as localized everywhere by @Ismatulla in #5349
  • fix(create): use real storefront port in scaffolded vendure-config by @wakqasahmed in #5338
  • fix(dashboard): working interpolation and plurals in dashboard extension by @tbouliere-datasolution in #5131
  • test(core): cover order-level side-effect promotions being kept by @brmk in #5321
  • ci: label dependency pull requests by published contract impact by @michaelbromley in #5231
  • ci: grant pull-requests write to the dependency impact workflow by @michaelbromley in #5362
  • fix(dashboard): add json import attributes when compiling in esm mode by @brbousnguar in #5361
  • fix(testing): avoid races creating the sqljs data directory by @sridharkalaibala in #5363
  • fix(dashboard): restore display component precedence for additionalColumns by @wakqasahmed in #5366
  • fix(dashboard): warn when an extension route collides with an existing route by @wakqasahmed in #5365
  • fix(dashboard): warn when editing shared option groups by @sridharkalaibala in #5364
  • chore(deps): bump actions/checkout from 5.0.1 to 7.0.1 in the github-actions group by @dependabot[bot] in #5375
  • chore(deps): bump the linting group with 2 updates by @dependabot[bot] in #5376
  • chore(deps): bump @nx/nx-darwin-x64 from 17.2.8 to 17.3.2 by @dependabot[bot] in #5382
  • chore(deps-dev): bump @gql.tada/cli-utils from 1.7.2 to 1.9.3 by @dependabot[bot] in #5383
  • fix(core): poll for RUNNING job instead of asserting immediately in job-queue e2e by @wakqasahmed in #5374
  • fix(dashboard): remove "orderState." prefix from French translations by @mhouel-coexya in #5387
  • fix(dashboard): allow typing in dialogs opened from an open bulk-actions menu by @michaelbromley in #5397
  • perf(dashboard): fix slow move-collections dialog on deeply nested trees by @michaelbromley in #5399
  • test(core): stop job-queue service spec leaking state between tests by @ivanvasyliuk in #5396
  • fix(job-queue-plugin): avoid loading retained payloads during startup indexing by @Lennix in #5297
  • fix(core): count totalItems before pagination in in-memory findMany by @brmk in #5394
  • fix(ci): fail the back-merge run when the merge conflicts by @michaelbromley in #5409
  • chore(deps-dev): bump eslint-plugin-react-refresh from 0.5.6 to 0.5.7 in the linting group by @dependabot[bot] in #5417
  • chore(ci): disable Dependabot by @michaelbromley in #5436
  • fix(core): stop EntityHydrator hanging when re-merging a ShippingMethod by @TheHypnoo in #5429
  • test(core): share default concurrency and set test job buffer id by @biggamesmallworld in #5407
  • perf(core): batch stockLevels lookups per request by @ivanvasyliuk in #5360
  • fix(repo): align @typescript-eslint/parser with eslint-plugin v8 by @biggamesmallworld in #5372
  • fix(core): dedupe variant ids from an OR-ed variant-name-filter by @brmk in #5416
  • fix(create): point scaffolded email URLs at the starters' real routes by @biggamesmallworld in #5401
  • ci: run tests when a pull request changes workflow files by @biggamesmallworld in #5402
  • test(create): cover the interactive prompt paths by @biggamesmallworld in #5404
  • fix(create): exit when the Docker fallback prompt is cancelled by @biggamesmallworld in #5405
  • fix(asset-server-plugin): use forward slashes in asset identifiers on Windows by @biggamesmallworld in #5403
  • fix(core): return job snapshots from in-memory job queue strategy by @brmk in #5293
  • fix(dashboard): keep create forms valid with non-nullable custom fields by @brmk in #5242
  • fix(email-plugin): recreate nodemailer transport when options can't be compared by @wakqasahmed in #5412
  • fix(core): correct inverse side of CustomerGroup.taxRates relation by @MillisJn in #5176
  • test(dashboard): cover useGeneratedForm create and update validity with a non-nullable custom field by @biggamesmallworld in #5441
  • test(core): assert channelId param in stockLevels channel test, fix ordering comments by @biggamesmallworld in #5437
  • fix(core): keep a job cancelled while running as cancelled when process returns by @biggamesmallworld in #5444
  • fix(dashboard): add customer email column to the order list by @biggamesmallworld in #5443
  • fix(dashboard): build the router once per page load by @dlhck in #5459
  • fix(dashboard): resolve Country custom fields from the Region config by @ivanvasyliuk in #5449
  • docs: limit security support to the latest 3.x minor by @HouseinIsProgramming in #5461
  • test(core): pass database role ids to ApiKeyService in api-key authz e2e by @gabriellbui in #5469
  • fix(core): key in-memory job store by string id by @biggamesmallworld in #5452
  • fix(core): add custom fields config entries for plugin entities before plugin configuration by @biggamesmallworld in #5455
  • docs: fix broken default email handlers link in email guide by @brbousnguar in #5471
  • fix(dashboard): translate the order line field names by @atistrcsn in #5464
  • fix(dashboard): use moneyStrategyPrecision for money input display, blur and step by @biggamesmallworld in #5453
  • fix(core): correct productVariantCount on nested Collection children by @michaelbromley in #5478
  • test(core): make the interleaved stock update test independent of statement order by @michaelbromley in #5485

New Contributors

Full Changelog: v3.7.3...v3.7.4

Don't miss a new vendure release

NewReleases is sending notifications on new releases.