- Shipped
- September 17, 2026 at 4:26 AM UTC
- Author
- Kamo
- Commit
- 6f51fa9
The retail provider-config handlers on CommerceMarketController resolved the session's org and then used whatever market and config uid the path named. Update, delete and the sync logs accepted a session from ANY organization; test-connection and sync accepted no session at all **************** lets a sessionless request through, and both handlers ignored the org they resolved). A store connection holds the store's API key, secret or access token, and test-connection calls the stored storeUrl with them, so anyone who knew a config's uid could repoint its store URL at their own server and have the token delivered there, or trigger syncs against another tenant's store. Create also linked a config to any market id, including another organization's. Every handler (list for a market, list for the org, create, update, delete, sync logs, test-connection, sync) now runs refuseRetailProviderAccess first: - 401 without a session; - 403 without MANAGE_SUBSCRIPTION_SETTINGS, the gate of /settings/features/pos, the only screen that reads or writes these connections (CommerceProviderSetup, its config dialog and sync dashboard), and the read tier of the sibling billing-provider connection on the same page; - 404 unless the market belongs to the session's organization **************** and, when a config is named, the config belongs to that organization AND that market. Another tenant's market or config answers like a missing one. Sync logs now require configId; without it the lookup was by a NULL config. RetailService and RetailSyncOrchestrator live in kamo-shared-library and still look configs up by uid alone; the ownership check runs here, before any of them is called, and nothing else in any service calls them with a caller-supplied uid (the webhook path resolves its own config and verifies the provider HMAC, fail-closed). Callers mapped: kamo-internal commerceProviderApi.ts only (commerceApi.ts's retail provider methods have no users; no mobile, kamo-js or service callers). Every call passes the config's own marketId from its DTO and runs on the **************** page. MarketOverviewTab and the new-market page also list configs and catch errors, so a member without the right who reaches a market page sees no linked providers instead of the list. RetailProviderConfigAccessTest drives every handler: anonymous 401 and a member without the right 403, both with nothing touched; another org's market 404; another org's config under this org's market 404 and a config under the wrong own market 404, with nothing updated, deleted, tested, synced or read; the manager path 200 on all eight. Removing each of the five checks fails exactly the test written for it.