@porulle/core 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/dist/auth/auth-schema-guard.d.ts +2444 -0
  2. package/dist/auth/auth-schema-guard.d.ts.map +1 -0
  3. package/dist/auth/auth-schema-guard.js +113 -0
  4. package/dist/auth/auth-schema.d.ts +228 -0
  5. package/dist/auth/auth-schema.d.ts.map +1 -1
  6. package/dist/auth/auth-schema.js +24 -2
  7. package/dist/auth/middleware.d.ts +1 -0
  8. package/dist/auth/middleware.d.ts.map +1 -1
  9. package/dist/auth/middleware.js +13 -5
  10. package/dist/auth/org.d.ts +8 -2
  11. package/dist/auth/org.d.ts.map +1 -1
  12. package/dist/auth/org.js +12 -4
  13. package/dist/auth/organization-guard.d.ts +5 -0
  14. package/dist/auth/organization-guard.d.ts.map +1 -0
  15. package/dist/auth/organization-guard.js +124 -0
  16. package/dist/auth/permissions.d.ts +10 -0
  17. package/dist/auth/permissions.d.ts.map +1 -1
  18. package/dist/auth/permissions.js +31 -7
  19. package/dist/auth/role-authority.d.ts +44 -0
  20. package/dist/auth/role-authority.d.ts.map +1 -0
  21. package/dist/auth/role-authority.js +85 -0
  22. package/dist/auth/setup.d.ts.map +1 -1
  23. package/dist/auth/setup.js +17 -0
  24. package/dist/auth/strict-org-resolution.d.ts.map +1 -1
  25. package/dist/auth/strict-org-resolution.js +9 -1
  26. package/dist/auth/types.d.ts +1 -1
  27. package/dist/auth/types.d.ts.map +1 -1
  28. package/dist/config/defaults.d.ts.map +1 -1
  29. package/dist/config/defaults.js +2 -0
  30. package/dist/config/types.d.ts +30 -4
  31. package/dist/config/types.d.ts.map +1 -1
  32. package/dist/hooks/checkout.d.ts.map +1 -1
  33. package/dist/hooks/checkout.js +10 -9
  34. package/dist/index.d.ts +6 -5
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +4 -3
  37. package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
  38. package/dist/interfaces/rest/audit-middleware.js +2 -0
  39. package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
  40. package/dist/interfaces/rest/customer-portal.js +9 -8
  41. package/dist/interfaces/rest/route-coverage.d.ts +14 -0
  42. package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
  43. package/dist/interfaces/rest/route-coverage.js +81 -0
  44. package/dist/interfaces/rest/router.d.ts +7 -2
  45. package/dist/interfaces/rest/router.d.ts.map +1 -1
  46. package/dist/interfaces/rest/router.js +8 -4
  47. package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
  48. package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
  49. package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +2 -2
  50. package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
  51. package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
  52. package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
  53. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
  54. package/dist/interfaces/rest/routes/admin/staff.js +126 -66
  55. package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
  56. package/dist/interfaces/rest/routes/audit.js +3 -3
  57. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  58. package/dist/interfaces/rest/routes/carts.js +13 -10
  59. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  60. package/dist/interfaces/rest/routes/catalog.js +10 -2
  61. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  62. package/dist/interfaces/rest/routes/checkout.js +30 -35
  63. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
  64. package/dist/interfaces/rest/routes/documents.js +10 -5
  65. package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
  66. package/dist/interfaces/rest/routes/inventory.js +2 -0
  67. package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
  68. package/dist/interfaces/rest/routes/media.js +7 -3
  69. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  70. package/dist/interfaces/rest/routes/orders.js +26 -8
  71. package/dist/interfaces/rest/routes/pricing.js +2 -2
  72. package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
  73. package/dist/interfaces/rest/routes/promotions.js +13 -10
  74. package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
  75. package/dist/interfaces/rest/routes/search.js +2 -1
  76. package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
  77. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  78. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  79. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  80. package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
  81. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  82. package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
  83. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  84. package/dist/interfaces/rest/utils.d.ts +15 -0
  85. package/dist/interfaces/rest/utils.d.ts.map +1 -1
  86. package/dist/interfaces/rest/utils.js +45 -2
  87. package/dist/kernel/compensation/executor.js +2 -2
  88. package/dist/kernel/database/tx-context.d.ts +8 -0
  89. package/dist/kernel/database/tx-context.d.ts.map +1 -1
  90. package/dist/kernel/database/tx-context.js +13 -0
  91. package/dist/kernel/hooks/create-context.d.ts +2 -0
  92. package/dist/kernel/hooks/create-context.d.ts.map +1 -1
  93. package/dist/kernel/hooks/create-context.js +1 -0
  94. package/dist/kernel/hooks/types.d.ts +2 -0
  95. package/dist/kernel/hooks/types.d.ts.map +1 -1
  96. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  97. package/dist/kernel/plugin/manifest.js +9 -5
  98. package/dist/modules/analytics/service.js +2 -2
  99. package/dist/modules/audit/service.js +3 -3
  100. package/dist/modules/cart/repository/index.d.ts +13 -7
  101. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  102. package/dist/modules/cart/repository/index.js +16 -10
  103. package/dist/modules/cart/schema.d.ts +17 -0
  104. package/dist/modules/cart/schema.d.ts.map +1 -1
  105. package/dist/modules/cart/schema.js +4 -0
  106. package/dist/modules/cart/service.d.ts +22 -9
  107. package/dist/modules/cart/service.d.ts.map +1 -1
  108. package/dist/modules/cart/service.js +57 -33
  109. package/dist/modules/catalog/brand-service.js +7 -7
  110. package/dist/modules/catalog/category-service.js +7 -7
  111. package/dist/modules/catalog/entity-service.d.ts +14 -7
  112. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  113. package/dist/modules/catalog/entity-service.js +109 -31
  114. package/dist/modules/catalog/read-policy.d.ts +9 -0
  115. package/dist/modules/catalog/read-policy.d.ts.map +1 -0
  116. package/dist/modules/catalog/read-policy.js +8 -0
  117. package/dist/modules/catalog/repository/index.d.ts +3 -2
  118. package/dist/modules/catalog/repository/index.d.ts.map +1 -1
  119. package/dist/modules/catalog/repository/index.js +14 -4
  120. package/dist/modules/catalog/service.d.ts +15 -13
  121. package/dist/modules/catalog/service.d.ts.map +1 -1
  122. package/dist/modules/catalog/service.js +25 -18
  123. package/dist/modules/customers/module.d.ts.map +1 -1
  124. package/dist/modules/customers/module.js +1 -0
  125. package/dist/modules/customers/service.d.ts +2 -0
  126. package/dist/modules/customers/service.d.ts.map +1 -1
  127. package/dist/modules/customers/service.js +21 -20
  128. package/dist/modules/documents/module.d.ts.map +1 -1
  129. package/dist/modules/documents/module.js +1 -0
  130. package/dist/modules/documents/service.d.ts +18 -4
  131. package/dist/modules/documents/service.d.ts.map +1 -1
  132. package/dist/modules/documents/service.js +54 -16
  133. package/dist/modules/fulfillment/module.d.ts.map +1 -1
  134. package/dist/modules/fulfillment/module.js +1 -0
  135. package/dist/modules/fulfillment/service.d.ts +2 -0
  136. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  137. package/dist/modules/fulfillment/service.js +15 -6
  138. package/dist/modules/inventory/service.d.ts.map +1 -1
  139. package/dist/modules/inventory/service.js +15 -14
  140. package/dist/modules/media/service.js +6 -6
  141. package/dist/modules/orders/guest-access.d.ts +27 -0
  142. package/dist/modules/orders/guest-access.d.ts.map +1 -0
  143. package/dist/modules/orders/guest-access.js +46 -0
  144. package/dist/modules/orders/module.d.ts.map +1 -1
  145. package/dist/modules/orders/module.js +1 -0
  146. package/dist/modules/orders/quote.d.ts.map +1 -1
  147. package/dist/modules/orders/quote.js +1 -0
  148. package/dist/modules/orders/repository/index.d.ts +1 -1
  149. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  150. package/dist/modules/orders/repository/index.js +2 -2
  151. package/dist/modules/orders/schema.d.ts +17 -0
  152. package/dist/modules/orders/schema.d.ts.map +1 -1
  153. package/dist/modules/orders/schema.js +5 -3
  154. package/dist/modules/orders/service.d.ts +16 -7
  155. package/dist/modules/orders/service.d.ts.map +1 -1
  156. package/dist/modules/orders/service.js +184 -55
  157. package/dist/modules/pricing/module.d.ts.map +1 -1
  158. package/dist/modules/pricing/module.js +1 -0
  159. package/dist/modules/pricing/service.d.ts +2 -0
  160. package/dist/modules/pricing/service.d.ts.map +1 -1
  161. package/dist/modules/pricing/service.js +12 -11
  162. package/dist/modules/promotions/module.d.ts.map +1 -1
  163. package/dist/modules/promotions/module.js +1 -0
  164. package/dist/modules/promotions/service.d.ts +3 -0
  165. package/dist/modules/promotions/service.d.ts.map +1 -1
  166. package/dist/modules/promotions/service.js +19 -11
  167. package/dist/modules/search/adapter.d.ts +6 -0
  168. package/dist/modules/search/adapter.d.ts.map +1 -1
  169. package/dist/modules/search/module.d.ts.map +1 -1
  170. package/dist/modules/search/module.js +1 -0
  171. package/dist/modules/search/service.d.ts +1 -0
  172. package/dist/modules/search/service.d.ts.map +1 -1
  173. package/dist/modules/search/service.js +57 -6
  174. package/dist/modules/settings/module.d.ts.map +1 -1
  175. package/dist/modules/settings/module.js +1 -0
  176. package/dist/modules/settings/service.d.ts +3 -0
  177. package/dist/modules/settings/service.d.ts.map +1 -1
  178. package/dist/modules/settings/service.js +6 -4
  179. package/dist/modules/shipping/service.js +9 -9
  180. package/dist/modules/tax/module.d.ts.map +1 -1
  181. package/dist/modules/tax/module.js +1 -0
  182. package/dist/modules/tax/service.d.ts +3 -0
  183. package/dist/modules/tax/service.d.ts.map +1 -1
  184. package/dist/modules/tax/service.js +11 -9
  185. package/dist/modules/webhooks/hook.js +2 -2
  186. package/dist/modules/webhooks/module.d.ts.map +1 -1
  187. package/dist/modules/webhooks/module.js +1 -0
  188. package/dist/modules/webhooks/service.d.ts +2 -0
  189. package/dist/modules/webhooks/service.d.ts.map +1 -1
  190. package/dist/modules/webhooks/service.js +4 -4
  191. package/dist/runtime/server.d.ts.map +1 -1
  192. package/dist/runtime/server.js +10 -2
  193. package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
  194. package/dist/test-utils/create-pglite-adapter.js +35 -13
  195. package/dist/test-utils/create-test-config.d.ts.map +1 -1
  196. package/dist/test-utils/create-test-config.js +4 -0
  197. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  198. package/dist/test-utils/rest-api-test-utils.js +2 -0
  199. package/dist/test-utils/test-actors.js +2 -2
  200. package/dist/utils/id.d.ts +2 -0
  201. package/dist/utils/id.d.ts.map +1 -1
  202. package/dist/utils/id.js +11 -0
  203. package/package.json +6 -5
  204. package/src/auth/auth-schema-guard.ts +143 -0
  205. package/src/auth/auth-schema.ts +50 -19
  206. package/src/auth/middleware.ts +17 -5
  207. package/src/auth/org.ts +15 -3
  208. package/src/auth/organization-guard.ts +158 -0
  209. package/src/auth/permissions.ts +31 -8
  210. package/src/auth/role-authority.ts +107 -0
  211. package/src/auth/setup.ts +17 -0
  212. package/src/auth/strict-org-resolution.ts +7 -1
  213. package/src/auth/types.ts +1 -1
  214. package/src/config/defaults.ts +2 -0
  215. package/src/config/types.ts +29 -4
  216. package/src/hooks/checkout.ts +19 -12
  217. package/src/index.ts +16 -6
  218. package/src/interfaces/rest/audit-middleware.ts +2 -0
  219. package/src/interfaces/rest/customer-portal.ts +31 -24
  220. package/src/interfaces/rest/route-coverage.ts +103 -0
  221. package/src/interfaces/rest/router.ts +16 -10
  222. package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
  223. package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +2 -2
  224. package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
  225. package/src/interfaces/rest/routes/admin/staff.ts +161 -72
  226. package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
  227. package/src/interfaces/rest/routes/audit.ts +3 -3
  228. package/src/interfaces/rest/routes/carts.ts +17 -7
  229. package/src/interfaces/rest/routes/catalog.ts +11 -0
  230. package/src/interfaces/rest/routes/checkout.ts +41 -38
  231. package/src/interfaces/rest/routes/documents.ts +15 -1
  232. package/src/interfaces/rest/routes/inventory.ts +2 -0
  233. package/src/interfaces/rest/routes/media.ts +8 -3
  234. package/src/interfaces/rest/routes/orders.ts +27 -8
  235. package/src/interfaces/rest/routes/pricing.ts +2 -2
  236. package/src/interfaces/rest/routes/promotions.ts +12 -9
  237. package/src/interfaces/rest/routes/search.ts +3 -1
  238. package/src/interfaces/rest/utils.ts +61 -2
  239. package/src/kernel/compensation/executor.ts +2 -2
  240. package/src/kernel/database/tx-context.ts +25 -0
  241. package/src/kernel/hooks/create-context.ts +3 -0
  242. package/src/kernel/hooks/types.ts +2 -0
  243. package/src/kernel/plugin/manifest.ts +9 -5
  244. package/src/modules/analytics/service.ts +2 -2
  245. package/src/modules/audit/service.ts +3 -3
  246. package/src/modules/cart/repository/index.ts +22 -8
  247. package/src/modules/cart/schema.ts +4 -0
  248. package/src/modules/cart/service.ts +62 -27
  249. package/src/modules/catalog/brand-service.ts +7 -7
  250. package/src/modules/catalog/category-service.ts +7 -7
  251. package/src/modules/catalog/entity-service.ts +117 -35
  252. package/src/modules/catalog/read-policy.ts +16 -0
  253. package/src/modules/catalog/repository/index.ts +19 -3
  254. package/src/modules/catalog/service.ts +46 -31
  255. package/src/modules/customers/module.ts +2 -0
  256. package/src/modules/customers/service.ts +23 -22
  257. package/src/modules/documents/module.ts +2 -0
  258. package/src/modules/documents/service.ts +74 -11
  259. package/src/modules/fulfillment/module.ts +2 -0
  260. package/src/modules/fulfillment/service.ts +17 -6
  261. package/src/modules/inventory/service.ts +15 -24
  262. package/src/modules/media/service.ts +6 -6
  263. package/src/modules/orders/guest-access.ts +55 -0
  264. package/src/modules/orders/module.ts +1 -0
  265. package/src/modules/orders/quote.ts +1 -0
  266. package/src/modules/orders/repository/index.ts +2 -0
  267. package/src/modules/orders/schema.ts +5 -3
  268. package/src/modules/orders/service.ts +302 -52
  269. package/src/modules/pricing/module.ts +2 -0
  270. package/src/modules/pricing/service.ts +14 -16
  271. package/src/modules/promotions/module.ts +2 -0
  272. package/src/modules/promotions/service.ts +25 -16
  273. package/src/modules/search/adapter.ts +6 -0
  274. package/src/modules/search/module.ts +1 -0
  275. package/src/modules/search/service.ts +55 -6
  276. package/src/modules/settings/module.ts +2 -0
  277. package/src/modules/settings/service.ts +8 -4
  278. package/src/modules/shipping/service.ts +9 -9
  279. package/src/modules/tax/module.ts +1 -0
  280. package/src/modules/tax/service.ts +13 -9
  281. package/src/modules/webhooks/hook.ts +2 -2
  282. package/src/modules/webhooks/module.ts +2 -0
  283. package/src/modules/webhooks/service.ts +6 -4
  284. package/src/runtime/server.ts +16 -2
  285. package/src/test-utils/create-pglite-adapter.ts +34 -12
  286. package/src/test-utils/create-test-config.ts +10 -6
  287. package/src/test-utils/rest-api-test-utils.ts +2 -0
  288. package/src/test-utils/test-actors.ts +2 -2
  289. package/src/utils/id.ts +20 -0
@@ -1,6 +1,6 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  import type { Kernel } from "../../runtime/kernel.js";
3
- import { assertPermission } from "../../auth/permissions.js";
3
+ import { assertPermission, requireUserId } from "../../auth/permissions.js";
4
4
  import type { Actor } from "../../auth/types.js";
5
5
  import type { AppEnv } from "./utils.js";
6
6
  import {
@@ -16,27 +16,30 @@ import {
16
16
  updateProfileRoute,
17
17
  createAddressRoute,
18
18
  } from "./schemas/customer-portal.js";
19
- import { isUUID, mapErrorToStatus } from "./utils.js";
19
+ import { isUUID, mapErrorToStatus, markRoutePermissionGuard } from "./utils.js";
20
+
21
+ type AuthenticatedActor = Actor & { userId: string };
20
22
 
21
23
  export function createCustomerPortalRoutes(kernel: Kernel) {
22
24
  const router = new OpenAPIHono<AppEnv>();
23
25
 
24
- router.use("*", async (c, next) => {
25
- if (!c.get("actor")) {
26
+ router.use("*", markRoutePermissionGuard(async (c, next) => {
27
+ const actor = c.get("actor") as Actor | null;
28
+ if (!actor?.userId) {
26
29
  return c.json(
27
30
  { error: { code: "FORBIDDEN", message: "Authentication required." } },
28
31
  401,
29
32
  );
30
33
  }
31
34
  await next();
32
- });
35
+ }));
33
36
 
34
37
  /**
35
38
  * Resolves an actor whose userId is the customer profile UUID (not the Better Auth user ID).
36
39
  * Required so that `assertOwnership(actor, order.customerId)` compares UUIDs correctly,
37
40
  * since orders.customer_id stores the customer profile UUID, not the Better Auth string ID.
38
41
  */
39
- async function resolveCustomerActor(actor: Actor): Promise<Actor | null> {
42
+ async function resolveCustomerActor(actor: AuthenticatedActor): Promise<AuthenticatedActor | null> {
40
43
  const customer = await kernel.services.customers.getByUserId(actor.userId, actor);
41
44
  if (!customer.ok) return null;
42
45
  return { ...actor, userId: customer.value.id };
@@ -44,7 +47,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
44
47
 
45
48
  // @ts-expect-error -- openapi handler union return type
46
49
  router.openapi(getProfileRoute, async (c) => {
47
- const actor = c.get("actor") as Actor;
50
+ const actor = c.get("actor") as AuthenticatedActor;
48
51
  const customer = await kernel.services.customers.getByUserId(actor.userId, actor);
49
52
  if (!customer.ok) return c.json({ error: customer.error }, 404);
50
53
  return c.json({ data: customer.value });
@@ -52,7 +55,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
52
55
 
53
56
  // @ts-expect-error -- openapi handler union return type
54
57
  router.openapi(updateProfileRoute, async (c) => {
55
- const actor = c.get("actor") as Actor;
58
+ const actor = c.get("actor") as AuthenticatedActor;
56
59
  assertPermission(actor, "customers:update:self");
57
60
  const result = await kernel.services.customers.updateByUserId(
58
61
  actor.userId,
@@ -64,7 +67,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
64
67
  });
65
68
 
66
69
  router.openapi(listAddressesRoute, async (c) => {
67
- const actor = c.get("actor") as Actor;
70
+ const actor = c.get("actor") as AuthenticatedActor;
68
71
  const addresses = await kernel.services.customers.getAddresses(
69
72
  actor.userId,
70
73
  actor,
@@ -74,7 +77,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
74
77
 
75
78
  // @ts-expect-error -- openapi handler union return type
76
79
  router.openapi(createAddressRoute, async (c) => {
77
- const actor = c.get("actor") as Actor;
80
+ const actor = c.get("actor") as AuthenticatedActor;
78
81
  const result = await kernel.services.customers.addAddress(
79
82
  actor.userId,
80
83
  c.req.valid("json") as Parameters<typeof kernel.services.customers.addAddress>[1],
@@ -86,7 +89,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
86
89
 
87
90
  // @ts-expect-error -- openapi handler union return type
88
91
  router.openapi(deleteAddressRoute, async (c) => {
89
- const actor = c.get("actor") as Actor;
92
+ const actor = c.get("actor") as AuthenticatedActor;
90
93
  const result = await kernel.services.customers.deleteAddress(
91
94
  actor.userId,
92
95
  c.req.param("id"),
@@ -98,23 +101,27 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
98
101
 
99
102
  // @ts-expect-error -- openapi handler union return type
100
103
  router.openapi(listCustomerOrdersRoute, async (c) => {
101
- const actor = c.get("actor") as Actor;
104
+ const actor = c.get("actor") as AuthenticatedActor;
102
105
  const status = c.req.query("status");
103
106
  // Resolve customer profile UUID from Better Auth userId
104
107
  const customerResult = await kernel.services.customers.getByUserId(actor.userId, actor);
105
108
  if (!customerResult.ok) return c.json({ data: [], meta: { total: 0, page: 1, limit: 20, totalPages: 0 } });
106
- const result = await kernel.services.orders.listByCustomer(customerResult.value.id, {
107
- page: Number.parseInt(c.req.query("page") ?? "1", 10),
108
- limit: Number.parseInt(c.req.query("limit") ?? "20", 10),
109
- ...(status !== undefined ? { status } : {}),
110
- });
111
- if (!result.ok) return c.json({ error: result.error }, 500);
109
+ const result = await kernel.services.orders.listByCustomer(
110
+ customerResult.value.id,
111
+ {
112
+ page: Number.parseInt(c.req.query("page") ?? "1", 10),
113
+ limit: Number.parseInt(c.req.query("limit") ?? "20", 10),
114
+ ...(status !== undefined ? { status } : {}),
115
+ },
116
+ actor,
117
+ );
118
+ if (!result.ok) return c.json({ error: result.error }, mapErrorToStatus(result.error));
112
119
  return c.json({ data: result.value.items, meta: result.value.pagination });
113
120
  });
114
121
 
115
122
  // @ts-expect-error -- openapi handler union return type
116
123
  router.openapi(getCustomerOrderRoute, async (c) => {
117
- const actor = c.get("actor") as Actor;
124
+ const actor = c.get("actor") as AuthenticatedActor;
118
125
  const id = c.req.param("idOrNumber");
119
126
  const customerActor = await resolveCustomerActor(actor);
120
127
  if (!customerActor) return c.json({ error: { code: "NOT_FOUND", message: "Customer profile not found." } }, 404);
@@ -129,7 +136,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
129
136
 
130
137
  // @ts-expect-error -- openapi handler union return type
131
138
  router.openapi(getOrderTrackingRoute, async (c) => {
132
- const actor = c.get("actor") as Actor;
139
+ const actor = c.get("actor") as AuthenticatedActor;
133
140
  const id = c.req.param("idOrNumber");
134
141
  const customerActor = await resolveCustomerActor(actor);
135
142
  if (!customerActor) return c.json({ error: { code: "NOT_FOUND", message: "Customer profile not found." } }, 404);
@@ -167,7 +174,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
167
174
 
168
175
  // @ts-expect-error -- openapi handler union return type
169
176
  router.openapi(getOrderDownloadsRoute, async (c) => {
170
- const actor = c.get("actor") as Actor;
177
+ const actor = c.get("actor") as AuthenticatedActor;
171
178
  const customerActor = await resolveCustomerActor(actor);
172
179
  if (!customerActor) return c.json({ error: { code: "NOT_FOUND", message: "Customer profile not found." } }, 404);
173
180
  const orderResult = await kernel.services.orders.getById(
@@ -209,7 +216,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
209
216
 
210
217
  // @ts-expect-error -- openapi handler union return type
211
218
  router.openapi(listCoursesRoute, async (c) => {
212
- const actor = c.get("actor") as Actor;
219
+ const actor = c.get("actor") as AuthenticatedActor;
213
220
  const result = await kernel.services.fulfillment.getDigitalAccess(
214
221
  actor.userId,
215
222
  "course",
@@ -230,7 +237,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
230
237
 
231
238
  // @ts-expect-error -- openapi handler union return type
232
239
  router.openapi(reorderRoute, async (c) => {
233
- const actor = c.get("actor") as Actor;
240
+ const actor = c.get("actor") as AuthenticatedActor;
234
241
  const customerActor = await resolveCustomerActor(actor);
235
242
  if (!customerActor) return c.json({ error: { code: "NOT_FOUND", message: "Customer profile not found." } }, 404);
236
243
  const orderResult = await kernel.services.orders.getById(
@@ -246,7 +253,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
246
253
 
247
254
  const cartResult = await kernel.services.cart.create(
248
255
  {
249
- customerId: actor.userId,
256
+ customerId: requireUserId(actor),
250
257
  currency: orderResult.value.currency,
251
258
  },
252
259
  actor,
@@ -0,0 +1,103 @@
1
+ import type { RouterRoute } from "hono/types";
2
+ import { isRoutePermissionGuard } from "./utils.js";
3
+
4
+ export type PublicRoute = {
5
+ method: string;
6
+ path: string;
7
+ justification: string;
8
+ };
9
+
10
+ export const PUBLIC_ROUTES: readonly PublicRoute[] = [
11
+ { method: "GET", path: "/api/health", justification: "Load balancers need an unauthenticated liveness probe." },
12
+ { method: "GET", path: "/api/reference", justification: "The development API explorer is intentionally public when enabled." },
13
+ { method: "GET", path: "/api/doc", justification: "The generated OpenAPI document is public when documentation exposure is enabled." },
14
+ { method: "GET", path: "/api/doc-ext", justification: "The enriched OpenAPI document is public when documentation exposure is enabled." },
15
+ { method: "POST", path: "/api/carts", justification: "Anonymous storefront shoppers create secret-backed guest carts." },
16
+ { method: "GET", path: "/api/carts/:id", justification: "Guest cart reads require the cart secret; customer carts require ownership in the service." },
17
+ { method: "POST", path: "/api/carts/:id/items", justification: "Anonymous storefront shoppers add items to a secret-backed guest cart." },
18
+ { method: "PATCH", path: "/api/carts/:id/items/:itemId", justification: "Anonymous storefront shoppers change quantities in a secret-backed guest cart." },
19
+ { method: "DELETE", path: "/api/carts/:id/items/:itemId", justification: "Anonymous storefront shoppers remove items from a secret-backed guest cart." },
20
+ { method: "POST", path: "/api/checkout", justification: "Anonymous storefront checkout is authorized by the customer actor and cart/payment pipeline." },
21
+ { method: "GET", path: "/api/media/:id", justification: "Public media assets redirect to their configured public storage URL; signed URLs still require an actor in the handler." },
22
+ { method: "POST", path: "/api/promotions/validate", justification: "Storefront shoppers validate a promotion code before checkout; the route is rate-limited." },
23
+ { method: "POST", path: "/api/payments/webhook", justification: "The payment provider authenticates this endpoint with its signed webhook payload." },
24
+ ];
25
+
26
+ type RouteTableApp = {
27
+ routes: readonly RouterRoute[];
28
+ };
29
+
30
+ type ComposedRouteHandler = {
31
+ __COMPOSED_HANDLER?: unknown;
32
+ };
33
+
34
+ function hasPermissionGuard(handler: unknown, method?: string, seen = new Set<unknown>()): boolean {
35
+ if (typeof handler !== "function" || seen.has(handler)) return false;
36
+ seen.add(handler);
37
+ if (isRoutePermissionGuard(handler, method)) return true;
38
+ const composed = (handler as unknown as ComposedRouteHandler).__COMPOSED_HANDLER;
39
+ return hasPermissionGuard(composed, method, seen);
40
+ }
41
+
42
+ function splitPath(path: string): string[] {
43
+ return path.split("/").filter(Boolean);
44
+ }
45
+
46
+ function pathPatternCovers(pattern: string, target: string): boolean {
47
+ const patternParts = splitPath(pattern);
48
+ const targetParts = splitPath(target);
49
+ const wildcardIndex = patternParts.indexOf("*");
50
+ if (wildcardIndex >= 0) {
51
+ if (wildcardIndex !== patternParts.length - 1 || targetParts.length < wildcardIndex) return false;
52
+ } else if (patternParts.length !== targetParts.length) {
53
+ return false;
54
+ }
55
+
56
+ return patternParts.every((part, index) => {
57
+ if (part === "*") return true;
58
+ const targetPart = targetParts[index];
59
+ return part.startsWith(":") || part === targetPart;
60
+ });
61
+ }
62
+
63
+ function publicRouteCovers(route: RouterRoute): boolean {
64
+ return PUBLIC_ROUTES.some((publicRoute) =>
65
+ publicRoute.method === route.method && pathPatternCovers(publicRoute.path, route.path),
66
+ );
67
+ }
68
+
69
+ function routeKey(route: RouterRoute): string {
70
+ return `${route.method} ${route.path}`;
71
+ }
72
+
73
+ export function findUncoveredRoutes(app: RouteTableApp): string[] {
74
+ const routes = app.routes;
75
+ const guardedRoutes = routes
76
+ .map((route, index) => ({ route, index }))
77
+ .filter(({ route }) => route.method === "ALL" && hasPermissionGuard(route.handler));
78
+ const endpoints = new Map<string, { route: RouterRoute; index: number }>();
79
+
80
+ for (const [index, route] of routes.entries()) {
81
+ if (route.method === "ALL") continue;
82
+ const key = routeKey(route);
83
+ if (!endpoints.has(key)) endpoints.set(key, { route, index });
84
+ }
85
+
86
+ return [...endpoints.values()]
87
+ .filter(({ route, index }) => {
88
+ if (publicRouteCovers(route)) return false;
89
+ const directlyGuarded = hasPermissionGuard(route.handler, route.method);
90
+ const coveredByMiddleware = guardedRoutes.some(({ route: guard, index: guardIndex }) =>
91
+ guardIndex <= index && pathPatternCovers(guard.path, route.path) && hasPermissionGuard(guard.handler, route.method),
92
+ );
93
+ return !directlyGuarded && !coveredByMiddleware;
94
+ })
95
+ .map(({ route }) => routeKey(route));
96
+ }
97
+
98
+ export function assertRouteCoverage(app: RouteTableApp): void {
99
+ const uncovered = findUncoveredRoutes(app);
100
+ if (uncovered.length > 0) {
101
+ throw new Error(`Unclassified routes: ${uncovered.join(", ")}`);
102
+ }
103
+ }
@@ -28,10 +28,11 @@
28
28
 
29
29
  import { createRoute, z } from "@hono/zod-openapi";
30
30
  import { ErrorSchema } from "./schemas/shared.js";
31
- import { mapErrorToResponse, mapErrorToStatus } from "./utils.js";
31
+ import { mapErrorToResponse, mapErrorToStatus, markRoutePermissionGuard } from "./utils.js";
32
+ import type { CommerceConfig } from "../../config/types.js";
32
33
  import type { PluginRouteRegistration } from "../../kernel/plugin/manifest.js";
33
34
  import { createScopedDb } from "../../kernel/database/scoped-db.js";
34
- import { resolveOrgId } from "../../auth/org.js";
35
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
35
36
 
36
37
  // ─── Shared OpenAPI Error Responses ──────────────────────────────────────────
37
38
 
@@ -61,8 +62,8 @@ export interface RouteHandlerContext {
61
62
  /** Path parameters, auto-extracted from {id} segments. */
62
63
  params: Record<string, string>;
63
64
  /** Authenticated actor. Guaranteed non-null if .auth() or .permission() was called. */
64
- actor: { userId: string; role: string; permissions: string[]; vendorId?: string | null; [key: string]: unknown } | null;
65
- /** Resolved organization ID. Derived from actor.organizationId, falls back to DEFAULT_ORG_ID. */
65
+ actor: { userId: string | null; role: string; permissions: string[]; vendorId?: string | null; [key: string]: unknown } | null;
66
+ /** Resolved organization ID. Derived from actor.organizationId or the deployment config. */
66
67
  orgId: string;
67
68
  /** Kernel services (orders, cart, inventory, etc.) */
68
69
  services: Record<string, unknown>;
@@ -91,7 +92,7 @@ class RouteChain {
91
92
  private fullPath: string,
92
93
  private tag: string,
93
94
  private routesList: PluginRouteRegistration[],
94
- private pluginCtx?: { services: Record<string, unknown>; db: unknown },
95
+ private pluginCtx?: { config?: CommerceConfig; services: Record<string, unknown>; db: unknown },
95
96
  ) {
96
97
  const paramNames = extractPathParams(fullPath);
97
98
  if (paramNames.length > 0) {
@@ -214,6 +215,7 @@ class RouteChain {
214
215
 
215
216
  // Use plugin context if provided (plugin routes), fallback to kernel on Hono context (core routes)
216
217
  const kernel = pluginCtx ?? (ctx.get("kernel") as {
218
+ config?: CommerceConfig;
217
219
  services?: Record<string, unknown>;
218
220
  db?: unknown;
219
221
  } | undefined);
@@ -222,7 +224,7 @@ class RouteChain {
222
224
  // Plugin route handlers receive a db that auto-filters queries
223
225
  // and auto-stamps inserts with the actor's organizationId.
224
226
  const rawDb = kernel?.db;
225
- const orgId = resolveOrgId(actor);
227
+ const orgId = resolveOrgIdForCommerce(actor, kernel?.config);
226
228
  const scopedDb = rawDb ? createScopedDb(rawDb as Record<string, unknown>, orgId) : rawDb;
227
229
 
228
230
  const handlerCtx: RouteHandlerContext = {
@@ -262,7 +264,10 @@ class RouteChain {
262
264
  }
263
265
  };
264
266
 
265
- this.routesList.push({ openapi: routeConfig, handler: honoHandler as (...args: unknown[]) => unknown });
267
+ const registeredHandler = requireAuth || requiredPermission
268
+ ? markRoutePermissionGuard(honoHandler)
269
+ : honoHandler;
270
+ this.routesList.push({ openapi: routeConfig, handler: registeredHandler as (...args: unknown[]) => unknown });
266
271
  }
267
272
  }
268
273
 
@@ -271,12 +276,12 @@ class RouteChain {
271
276
  class RouterImpl {
272
277
  private _routes: PluginRouteRegistration[] = [];
273
278
  private _prefix: string;
274
- private _pluginCtx: { services: Record<string, unknown>; db: unknown } | undefined;
279
+ private _pluginCtx: { config?: CommerceConfig; services: Record<string, unknown>; db: unknown } | undefined;
275
280
 
276
281
  constructor(
277
282
  private tag: string,
278
283
  prefix: string,
279
- pluginCtx?: { services?: Record<string, unknown>; database?: { db: unknown } },
284
+ pluginCtx?: { config?: CommerceConfig; services?: Record<string, unknown>; database?: { db: unknown } },
280
285
  ) {
281
286
  // Normalize: ensure /api prefix, strip trailing slash
282
287
  let p = prefix.startsWith("/api") ? prefix : `/api${prefix}`;
@@ -286,6 +291,7 @@ class RouterImpl {
286
291
  // If plugin context is provided, wire services + db for handler access
287
292
  if (pluginCtx) {
288
293
  this._pluginCtx = {
294
+ ...(pluginCtx.config !== undefined ? { config: pluginCtx.config } : {}),
289
295
  services: pluginCtx.services ?? {},
290
296
  db: pluginCtx.database?.db,
291
297
  };
@@ -330,6 +336,6 @@ class RouterImpl {
330
336
  * }
331
337
  * ```
332
338
  */
333
- export function router(tag: string, prefix: string, ctx?: { services?: Record<string, unknown>; database?: { db: unknown } }): RouterImpl {
339
+ export function router(tag: string, prefix: string, ctx?: { config?: CommerceConfig; services?: Record<string, unknown>; database?: { db: unknown } }): RouterImpl {
334
340
  return new RouterImpl(tag, prefix, ctx);
335
341
  }
@@ -2,14 +2,14 @@ import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  import type { Kernel } from "../../../../runtime/kernel.js";
3
3
  import type { Actor } from "../../../../auth/types.js";
4
4
  import { assertPermission } from "../../../../auth/permissions.js";
5
- import { resolveOrgId } from "../../../../auth/org.js";
5
+ import { resolveOrgIdForCommerce } from "../../../../auth/org.js";
6
6
  import { CommerceValidationError } from "../../../../kernel/errors.js";
7
7
  import type { CompensationFailure } from "../../../../kernel/compensation/schema.js";
8
8
  import {
9
9
  listCompensationFailuresRoute,
10
10
  resolveCompensationFailureRoute,
11
11
  } from "../../schemas/compensation-failures.js";
12
- import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../../utils.js";
12
+ import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../../utils.js";
13
13
 
14
14
  function toIso(d: Date | null | undefined): string | null {
15
15
  if (d == null) return null;
@@ -67,11 +67,14 @@ function parseResolvedFilter(
67
67
  export function compensationFailureAdminRoutes(kernel: Kernel) {
68
68
  const router = new OpenAPIHono<AppEnv>();
69
69
 
70
+ router.use("/compensation-failures", requirePerm("compensation:admin"));
71
+ router.use("/compensation-failures/*", requirePerm("compensation:admin"));
72
+
70
73
  // @ts-expect-error -- openapi handler union return type
71
74
  router.openapi(listCompensationFailuresRoute, async (c) => {
72
75
  const actor = c.get("actor");
73
76
  assertPermission(actor, "compensation:admin");
74
- const orgId = resolveOrgId(actor);
77
+ const orgId = resolveOrgIdForCommerce(actor, kernel.config);
75
78
  const resolvedParsed = parseResolvedFilter(c.req.query("resolved"));
76
79
  if (!resolvedParsed.ok) {
77
80
  return c.json(
@@ -116,7 +119,7 @@ export function compensationFailureAdminRoutes(kernel: Kernel) {
116
119
  router.openapi(resolveCompensationFailureRoute, async (c) => {
117
120
  const actor = c.get("actor");
118
121
  assertPermission(actor, "compensation:admin");
119
- const orgId = resolveOrgId(actor);
122
+ const orgId = resolveOrgIdForCommerce(actor, kernel.config);
120
123
  const id = c.req.param("id");
121
124
  const body = c.req.valid("json");
122
125
 
@@ -147,7 +150,7 @@ export function compensationFailureAdminRoutes(kernel: Kernel) {
147
150
  const resolved = await kernel.services.compensationFailures.markResolved({
148
151
  id,
149
152
  organizationId: orgId,
150
- resolvedBy: (actor as Actor).userId,
153
+ resolvedBy: (actor as Actor).userId ?? "system",
151
154
  ...(body.notes !== undefined ? { notes: body.notes } : {}),
152
155
  });
153
156
  if (!resolved.ok) {
@@ -1,6 +1,6 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  import type { Kernel } from "../../../../runtime/kernel.js";
3
- import { resolveOrgId } from "../../../../auth/org.js";
3
+ import { resolveOrgIdForCommerce } from "../../../../auth/org.js";
4
4
  import {
5
5
  listCustomFieldProposalsRoute,
6
6
  } from "../../schemas/admin-custom-field-proposals.js";
@@ -19,7 +19,7 @@ export function adminCustomFieldProposalRoutes(kernel: Kernel) {
19
19
  const pagination = parsePagination(c.req.query());
20
20
  const entityType = c.req.query("entityType");
21
21
  const result = await kernel.services.catalog.repository.listProposedCustomFields(
22
- resolveOrgId(c.get("actor")),
22
+ resolveOrgIdForCommerce(c.get("actor"), kernel.config),
23
23
  pagination,
24
24
  entityType !== undefined ? { entityType } : undefined,
25
25
  );
@@ -3,7 +3,7 @@ import type { Kernel } from "../../../../runtime/kernel.js";
3
3
  import type { CommerceConfig } from "../../../../config/types.js";
4
4
  import { assertPermission } from "../../../../auth/permissions.js";
5
5
  import { listAdminPermissionsRoute } from "../../schemas/admin-permissions.js";
6
- import type { AppEnv } from "../../utils.js";
6
+ import { type AppEnv, requirePerm } from "../../utils.js";
7
7
 
8
8
  function flattenCoreRolePermissions(config: CommerceConfig): string[] {
9
9
  const roles = config.auth?.roles ?? {};
@@ -19,6 +19,8 @@ function flattenCoreRolePermissions(config: CommerceConfig): string[] {
19
19
  export function adminPermissionsRoutes(kernel: Kernel) {
20
20
  const router = new OpenAPIHono<AppEnv>();
21
21
 
22
+ router.use("/permissions", requirePerm("admin"));
23
+
22
24
  router.openapi(listAdminPermissionsRoute, (c) => {
23
25
  const actor = c.get("actor");
24
26
  assertPermission(actor, "admin");