@porulle/core 0.10.8 → 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.
- package/dist/auth/auth-schema-guard.d.ts +2444 -0
- package/dist/auth/auth-schema-guard.d.ts.map +1 -0
- package/dist/auth/auth-schema-guard.js +113 -0
- package/dist/auth/auth-schema.d.ts +228 -0
- package/dist/auth/auth-schema.d.ts.map +1 -1
- package/dist/auth/auth-schema.js +24 -2
- package/dist/auth/middleware.d.ts +1 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +13 -5
- package/dist/auth/org.d.ts +8 -2
- package/dist/auth/org.d.ts.map +1 -1
- package/dist/auth/org.js +12 -4
- package/dist/auth/organization-guard.d.ts +5 -0
- package/dist/auth/organization-guard.d.ts.map +1 -0
- package/dist/auth/organization-guard.js +124 -0
- package/dist/auth/permissions.d.ts +10 -0
- package/dist/auth/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +31 -7
- package/dist/auth/role-authority.d.ts +44 -0
- package/dist/auth/role-authority.d.ts.map +1 -0
- package/dist/auth/role-authority.js +85 -0
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +17 -0
- package/dist/auth/strict-org-resolution.d.ts.map +1 -1
- package/dist/auth/strict-org-resolution.js +9 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +33 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +10 -9
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
- package/dist/interfaces/rest/audit-middleware.js +2 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/customer-portal.js +9 -8
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +4 -0
- package/dist/interfaces/rest/route-coverage.d.ts +14 -0
- package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
- package/dist/interfaces/rest/route-coverage.js +81 -0
- package/dist/interfaces/rest/router.d.ts +7 -2
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/router.js +8 -4
- package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
- package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/staff.js +126 -66
- package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
- package/dist/interfaces/rest/routes/audit.js +3 -3
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +13 -10
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +47 -4
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +30 -35
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/documents.js +10 -5
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +2 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +7 -3
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +26 -8
- package/dist/interfaces/rest/routes/pricing.js +2 -2
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +13 -10
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +14 -1
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +86 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +1 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.js +1 -0
- package/dist/interfaces/rest/utils.d.ts +15 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.js +45 -2
- package/dist/kernel/compensation/executor.js +2 -2
- package/dist/kernel/database/tx-context.d.ts +8 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -1
- package/dist/kernel/database/tx-context.js +13 -0
- package/dist/kernel/hooks/create-context.d.ts +2 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -1
- package/dist/kernel/hooks/create-context.js +1 -0
- package/dist/kernel/hooks/types.d.ts +2 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +9 -5
- package/dist/modules/analytics/service.js +2 -2
- package/dist/modules/audit/service.js +3 -3
- package/dist/modules/cart/repository/index.d.ts +13 -7
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +16 -10
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +4 -0
- package/dist/modules/cart/service.d.ts +22 -9
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +57 -33
- package/dist/modules/catalog/brand-service.js +7 -7
- package/dist/modules/catalog/category-service.js +7 -7
- package/dist/modules/catalog/entity-service.d.ts +19 -10
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +180 -51
- package/dist/modules/catalog/module.d.ts +6 -1
- package/dist/modules/catalog/module.d.ts.map +1 -1
- package/dist/modules/catalog/module.js +6 -1
- package/dist/modules/catalog/ownership.d.ts +5 -0
- package/dist/modules/catalog/ownership.d.ts.map +1 -0
- package/dist/modules/catalog/ownership.js +11 -0
- package/dist/modules/catalog/read-policy.d.ts +9 -0
- package/dist/modules/catalog/read-policy.d.ts.map +1 -0
- package/dist/modules/catalog/read-policy.js +8 -0
- package/dist/modules/catalog/repository/index.d.ts +55 -3
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +335 -6
- package/dist/modules/catalog/revision.d.ts +4 -0
- package/dist/modules/catalog/revision.d.ts.map +1 -0
- package/dist/modules/catalog/revision.js +33 -0
- package/dist/modules/catalog/schema.d.ts +979 -110
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +112 -2
- package/dist/modules/catalog/schemas.d.ts +2 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +2 -0
- package/dist/modules/catalog/service.d.ts +80 -15
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +522 -14
- package/dist/modules/channels/adapter.d.ts +85 -0
- package/dist/modules/channels/adapter.d.ts.map +1 -1
- package/dist/modules/channels/adapter.js +9 -1
- package/dist/modules/customers/module.d.ts.map +1 -1
- package/dist/modules/customers/module.js +1 -0
- package/dist/modules/customers/service.d.ts +2 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +21 -20
- package/dist/modules/documents/module.d.ts.map +1 -1
- package/dist/modules/documents/module.js +1 -0
- package/dist/modules/documents/service.d.ts +18 -4
- package/dist/modules/documents/service.d.ts.map +1 -1
- package/dist/modules/documents/service.js +54 -16
- package/dist/modules/fulfillment/module.d.ts.map +1 -1
- package/dist/modules/fulfillment/module.js +1 -0
- package/dist/modules/fulfillment/service.d.ts +2 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +15 -6
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +15 -14
- package/dist/modules/media/module.d.ts +68 -0
- package/dist/modules/media/module.d.ts.map +1 -1
- package/dist/modules/media/module.js +2 -0
- package/dist/modules/media/schema.d.ts +70 -0
- package/dist/modules/media/schema.d.ts.map +1 -1
- package/dist/modules/media/schema.js +20 -2
- package/dist/modules/media/service.d.ts +8 -1
- package/dist/modules/media/service.d.ts.map +1 -1
- package/dist/modules/media/service.js +71 -23
- package/dist/modules/orders/guest-access.d.ts +27 -0
- package/dist/modules/orders/guest-access.d.ts.map +1 -0
- package/dist/modules/orders/guest-access.js +46 -0
- package/dist/modules/orders/module.d.ts.map +1 -1
- package/dist/modules/orders/module.js +1 -0
- package/dist/modules/orders/quote.d.ts.map +1 -1
- package/dist/modules/orders/quote.js +1 -0
- package/dist/modules/orders/repository/index.d.ts +1 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +2 -2
- package/dist/modules/orders/schema.d.ts +17 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +5 -3
- package/dist/modules/orders/service.d.ts +16 -7
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +184 -55
- package/dist/modules/pricing/module.d.ts.map +1 -1
- package/dist/modules/pricing/module.js +1 -0
- package/dist/modules/pricing/schema.d.ts +17 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -1
- package/dist/modules/pricing/schema.js +1 -0
- package/dist/modules/pricing/schemas.d.ts +1 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -1
- package/dist/modules/pricing/schemas.js +1 -0
- package/dist/modules/pricing/service.d.ts +2 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +18 -12
- package/dist/modules/promotions/module.d.ts.map +1 -1
- package/dist/modules/promotions/module.js +1 -0
- package/dist/modules/promotions/service.d.ts +3 -0
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +19 -11
- package/dist/modules/search/adapter.d.ts +8 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +3 -0
- package/dist/modules/search/service.d.ts +5 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +131 -11
- package/dist/modules/settings/module.d.ts.map +1 -1
- package/dist/modules/settings/module.js +1 -0
- package/dist/modules/settings/service.d.ts +3 -0
- package/dist/modules/settings/service.d.ts.map +1 -1
- package/dist/modules/settings/service.js +6 -4
- package/dist/modules/shipping/service.js +9 -9
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +1 -0
- package/dist/modules/tax/service.d.ts +3 -0
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +11 -9
- package/dist/modules/webhooks/hook.js +2 -2
- package/dist/modules/webhooks/module.d.ts.map +1 -1
- package/dist/modules/webhooks/module.js +1 -0
- package/dist/modules/webhooks/service.d.ts +2 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -1
- package/dist/modules/webhooks/service.js +4 -4
- package/dist/runtime/kernel-modules.d.ts +73 -0
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +10 -2
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +35 -13
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +4 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +2 -0
- package/dist/test-utils/test-actors.js +2 -2
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +11 -0
- package/package.json +6 -5
- package/src/auth/auth-schema-guard.ts +143 -0
- package/src/auth/auth-schema.ts +50 -19
- package/src/auth/middleware.ts +17 -5
- package/src/auth/org.ts +15 -3
- package/src/auth/organization-guard.ts +158 -0
- package/src/auth/permissions.ts +31 -8
- package/src/auth/role-authority.ts +107 -0
- package/src/auth/setup.ts +17 -0
- package/src/auth/strict-org-resolution.ts +7 -1
- package/src/auth/types.ts +1 -1
- package/src/config/defaults.ts +2 -0
- package/src/config/types.ts +32 -4
- package/src/hooks/checkout.ts +19 -12
- package/src/index.ts +30 -6
- package/src/interfaces/rest/audit-middleware.ts +2 -0
- package/src/interfaces/rest/customer-portal.ts +31 -24
- package/src/interfaces/rest/index.ts +4 -0
- package/src/interfaces/rest/route-coverage.ts +103 -0
- package/src/interfaces/rest/router.ts +16 -10
- package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
- package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
- package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
- package/src/interfaces/rest/routes/admin/staff.ts +161 -72
- package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
- package/src/interfaces/rest/routes/audit.ts +3 -3
- package/src/interfaces/rest/routes/carts.ts +17 -7
- package/src/interfaces/rest/routes/catalog.ts +96 -0
- package/src/interfaces/rest/routes/checkout.ts +41 -38
- package/src/interfaces/rest/routes/documents.ts +15 -1
- package/src/interfaces/rest/routes/inventory.ts +2 -0
- package/src/interfaces/rest/routes/media.ts +8 -3
- package/src/interfaces/rest/routes/orders.ts +27 -8
- package/src/interfaces/rest/routes/pricing.ts +2 -2
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/routes/search.ts +19 -1
- package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
- package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
- package/src/interfaces/rest/schemas/catalog.ts +92 -0
- package/src/interfaces/rest/schemas/search.ts +2 -0
- package/src/interfaces/rest/utils.ts +61 -2
- package/src/kernel/compensation/executor.ts +2 -2
- package/src/kernel/database/tx-context.ts +25 -0
- package/src/kernel/hooks/create-context.ts +3 -0
- package/src/kernel/hooks/types.ts +2 -0
- package/src/kernel/plugin/manifest.ts +9 -5
- package/src/modules/analytics/service.ts +2 -2
- package/src/modules/audit/service.ts +3 -3
- package/src/modules/cart/repository/index.ts +22 -8
- package/src/modules/cart/schema.ts +4 -0
- package/src/modules/cart/service.ts +62 -27
- package/src/modules/catalog/brand-service.ts +7 -7
- package/src/modules/catalog/category-service.ts +7 -7
- package/src/modules/catalog/entity-service.ts +188 -53
- package/src/modules/catalog/module.ts +15 -0
- package/src/modules/catalog/ownership.ts +17 -0
- package/src/modules/catalog/read-policy.ts +16 -0
- package/src/modules/catalog/repository/index.ts +566 -4
- package/src/modules/catalog/revision.ts +35 -0
- package/src/modules/catalog/schema.ts +169 -1
- package/src/modules/catalog/schemas.ts +2 -0
- package/src/modules/catalog/service.ts +766 -26
- package/src/modules/channels/adapter.ts +107 -1
- package/src/modules/customers/module.ts +2 -0
- package/src/modules/customers/service.ts +23 -22
- package/src/modules/documents/module.ts +2 -0
- package/src/modules/documents/service.ts +74 -11
- package/src/modules/fulfillment/module.ts +2 -0
- package/src/modules/fulfillment/service.ts +17 -6
- package/src/modules/inventory/service.ts +15 -24
- package/src/modules/media/module.ts +2 -0
- package/src/modules/media/schema.ts +23 -2
- package/src/modules/media/service.ts +89 -28
- package/src/modules/orders/guest-access.ts +55 -0
- package/src/modules/orders/module.ts +1 -0
- package/src/modules/orders/quote.ts +1 -0
- package/src/modules/orders/repository/index.ts +2 -0
- package/src/modules/orders/schema.ts +5 -3
- package/src/modules/orders/service.ts +302 -52
- package/src/modules/pricing/module.ts +2 -0
- package/src/modules/pricing/schema.ts +1 -0
- package/src/modules/pricing/schemas.ts +1 -0
- package/src/modules/pricing/service.ts +21 -18
- package/src/modules/promotions/module.ts +2 -0
- package/src/modules/promotions/service.ts +25 -16
- package/src/modules/search/adapter.ts +8 -0
- package/src/modules/search/module.ts +3 -0
- package/src/modules/search/service.ts +142 -12
- package/src/modules/settings/module.ts +2 -0
- package/src/modules/settings/service.ts +8 -4
- package/src/modules/shipping/service.ts +9 -9
- package/src/modules/tax/module.ts +1 -0
- package/src/modules/tax/service.ts +13 -9
- package/src/modules/webhooks/hook.ts +2 -2
- package/src/modules/webhooks/module.ts +2 -0
- package/src/modules/webhooks/service.ts +6 -4
- package/src/runtime/server.ts +16 -2
- package/src/test-utils/create-pglite-adapter.ts +34 -12
- package/src/test-utils/create-test-config.ts +10 -6
- package/src/test-utils/rest-api-test-utils.ts +2 -0
- package/src/test-utils/test-actors.ts +2 -2
- package/src/utils/id.ts +20 -0
package/dist/utils/id.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Actor } from "../auth/types.js";
|
|
1
2
|
export declare function makeId(): string;
|
|
2
3
|
export declare function makeDeterministicId(value: string): Promise<string>;
|
|
4
|
+
export declare function makeIdempotencyScope(actor: Actor | null | undefined, guestCredential?: string, fallbackBinding?: string): Promise<string>;
|
|
3
5
|
//# sourceMappingURL=id.d.ts.map
|
package/dist/utils/id.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASxE"}
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASxE;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,EAC/B,eAAe,CAAC,EAAE,MAAM,EACxB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAYjB"}
|
package/dist/utils/id.js
CHANGED
|
@@ -9,3 +9,14 @@ export async function makeDeterministicId(value) {
|
|
|
9
9
|
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
10
10
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
11
11
|
}
|
|
12
|
+
export async function makeIdempotencyScope(actor, guestCredential, fallbackBinding) {
|
|
13
|
+
const binding = actor?.userId
|
|
14
|
+
? `actor:${actor.type}:${actor.userId}`
|
|
15
|
+
: guestCredential
|
|
16
|
+
? `guest:${guestCredential}`
|
|
17
|
+
: fallbackBinding
|
|
18
|
+
? `resource:${fallbackBinding}`
|
|
19
|
+
: "anonymous";
|
|
20
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(binding)));
|
|
21
|
+
return [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@better-auth/api-key": "^1.
|
|
40
|
-
"@better-auth/drizzle-adapter": "^1.
|
|
39
|
+
"@better-auth/api-key": "^1.7.1",
|
|
40
|
+
"@better-auth/drizzle-adapter": "^1.7.1",
|
|
41
41
|
"@hono/zod-openapi": "^1.2.2",
|
|
42
42
|
"@scalar/hono-api-reference": "^0.10.5",
|
|
43
|
-
"better-auth": "^1.
|
|
43
|
+
"better-auth": "^1.7.1",
|
|
44
44
|
"drizzle-orm": "^0.45.1",
|
|
45
45
|
"drizzle-zod": "^0.8.3",
|
|
46
46
|
"hono": "^4.9.8",
|
|
@@ -81,7 +81,8 @@
|
|
|
81
81
|
"scripts": {
|
|
82
82
|
"generate:plugins": "node ./scripts/generate-plugin-types.mjs",
|
|
83
83
|
"check:store-guard": "node ./scripts/check-store-guard.mjs",
|
|
84
|
-
"
|
|
84
|
+
"check:auth-schema": "vitest run test/auth-schema-better-auth-parity.test.ts",
|
|
85
|
+
"build": "rm -rf dist tsconfig.build.tsbuildinfo && npm run generate:plugins && npm run check:store-guard && npm run check:auth-schema && tsc -p tsconfig.build.json",
|
|
85
86
|
"check-types": "npm run generate:plugins && npm run check:store-guard && tsc --noEmit",
|
|
86
87
|
"lint": "eslint . --max-warnings 1000",
|
|
87
88
|
"test": "vitest run"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares Porulle's Drizzle auth tables against better-auth's canonical
|
|
3
|
+
* {@link getAuthTables} output so a dependency minor bump cannot add a column
|
|
4
|
+
* the declared schema omits.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getTableColumns, getTableName } from "drizzle-orm";
|
|
8
|
+
import type { PgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
9
|
+
import { getAuthTables } from "better-auth/db";
|
|
10
|
+
import type { BetterAuthOptions } from "better-auth";
|
|
11
|
+
import { apiKey } from "@better-auth/api-key";
|
|
12
|
+
import { organization, twoFactor, phoneNumber, jwt, bearer } from "better-auth/plugins";
|
|
13
|
+
import * as authSchema from "./auth-schema.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Every plugin {@link createAuth} can enable, not only the ones it enables by
|
|
17
|
+
* default. The shipped schema has to satisfy any configuration a merchant
|
|
18
|
+
* chooses, so the guard compares against the maximal table set: a column that
|
|
19
|
+
* only `twoFactor` needs is still a column this package must declare.
|
|
20
|
+
*/
|
|
21
|
+
export const AUTH_SCHEMA_GUARD_OPTIONS = {
|
|
22
|
+
plugins: [
|
|
23
|
+
organization({ roles: {} }),
|
|
24
|
+
bearer(),
|
|
25
|
+
jwt(),
|
|
26
|
+
twoFactor({ issuer: "porulle-auth-schema-guard" }),
|
|
27
|
+
apiKey(),
|
|
28
|
+
phoneNumber(),
|
|
29
|
+
],
|
|
30
|
+
user: {
|
|
31
|
+
additionalFields: {
|
|
32
|
+
vendorId: { type: "string", required: false },
|
|
33
|
+
posOperatorPin: { type: "string", required: false },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} satisfies BetterAuthOptions;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Drizzle tables keyed by the SQL table name they declare, so the guard walks
|
|
40
|
+
* whatever models better-auth reports rather than a list someone has to
|
|
41
|
+
* remember to extend. A hand-maintained model list is how `jwks.alg` shipped
|
|
42
|
+
* missing while this guard passed.
|
|
43
|
+
*/
|
|
44
|
+
function drizzleTablesByName(): Map<string, PgTable> {
|
|
45
|
+
const byName = new Map<string, PgTable>();
|
|
46
|
+
for (const value of Object.values(authSchema)) {
|
|
47
|
+
try {
|
|
48
|
+
byName.set(getTableName(value as PgTable), value as PgTable);
|
|
49
|
+
} catch {
|
|
50
|
+
// Not a Drizzle table (type export, index helper) — skip.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return byName;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function drizzleColumnForField(
|
|
57
|
+
table: PgTable,
|
|
58
|
+
fieldKey: string,
|
|
59
|
+
fieldName: string,
|
|
60
|
+
): PgColumn | undefined {
|
|
61
|
+
const columns = getTableColumns(table);
|
|
62
|
+
if (fieldKey in columns) {
|
|
63
|
+
return columns[fieldKey] as PgColumn;
|
|
64
|
+
}
|
|
65
|
+
for (const column of Object.values(columns)) {
|
|
66
|
+
if (column.name === fieldName) {
|
|
67
|
+
return column as PgColumn;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type AuthSchemaParityMismatch = {
|
|
74
|
+
model: string;
|
|
75
|
+
field: string;
|
|
76
|
+
issue: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns every mismatch between better-auth's expected auth tables and the
|
|
81
|
+
* Drizzle definitions Porulle ships. An empty array means parity holds.
|
|
82
|
+
*/
|
|
83
|
+
export function findAuthSchemaParityMismatches(
|
|
84
|
+
options: BetterAuthOptions = AUTH_SCHEMA_GUARD_OPTIONS,
|
|
85
|
+
): AuthSchemaParityMismatch[] {
|
|
86
|
+
const mismatches: AuthSchemaParityMismatch[] = [];
|
|
87
|
+
const authTables = getAuthTables(options);
|
|
88
|
+
const byName = drizzleTablesByName();
|
|
89
|
+
|
|
90
|
+
for (const [model, tableDef] of Object.entries(authTables)) {
|
|
91
|
+
if (!tableDef) continue;
|
|
92
|
+
const drizzleTable = byName.get(tableDef.modelName);
|
|
93
|
+
if (!drizzleTable) {
|
|
94
|
+
mismatches.push({
|
|
95
|
+
model,
|
|
96
|
+
field: "*",
|
|
97
|
+
issue: `no drizzle table declares better-auth model "${tableDef.modelName}"`,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const [fieldKey, fieldAttr] of Object.entries(tableDef.fields)) {
|
|
103
|
+
const column = drizzleColumnForField(
|
|
104
|
+
drizzleTable,
|
|
105
|
+
fieldKey,
|
|
106
|
+
fieldAttr.fieldName ?? fieldKey,
|
|
107
|
+
);
|
|
108
|
+
if (!column) {
|
|
109
|
+
mismatches.push({
|
|
110
|
+
model,
|
|
111
|
+
field: fieldKey,
|
|
112
|
+
issue: `drizzle column missing for better-auth field "${fieldKey}"`,
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const required = fieldAttr.required !== false;
|
|
118
|
+
if (required && !column.notNull) {
|
|
119
|
+
mismatches.push({
|
|
120
|
+
model,
|
|
121
|
+
field: fieldKey,
|
|
122
|
+
issue: `column "${column.name}" must be NOT NULL (better-auth required)`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return mismatches;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function assertAuthSchemaParity(
|
|
132
|
+
options: BetterAuthOptions = AUTH_SCHEMA_GUARD_OPTIONS,
|
|
133
|
+
): void {
|
|
134
|
+
const mismatches = findAuthSchemaParityMismatches(options);
|
|
135
|
+
if (mismatches.length === 0) return;
|
|
136
|
+
|
|
137
|
+
const detail = mismatches
|
|
138
|
+
.map((m) => ` ${m.model}.${m.field}: ${m.issue}`)
|
|
139
|
+
.join("\n");
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Porulle auth-schema.ts is out of sync with installed better-auth:\n${detail}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
package/src/auth/auth-schema.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
timestamp,
|
|
5
5
|
boolean,
|
|
6
6
|
integer,
|
|
7
|
+
uniqueIndex,
|
|
7
8
|
} from "drizzle-orm/pg-core";
|
|
8
9
|
|
|
9
10
|
export const user = pgTable("user", {
|
|
@@ -19,6 +20,12 @@ export const user = pgTable("user", {
|
|
|
19
20
|
.notNull(),
|
|
20
21
|
vendorId: text("vendor_id"),
|
|
21
22
|
posOperatorPin: text("pos_operator_pin"),
|
|
23
|
+
// Contributed by the twoFactor and phoneNumber plugins. Declared
|
|
24
|
+
// unconditionally: the plugins are config-gated, but one shipped schema has
|
|
25
|
+
// to satisfy every configuration a merchant can choose.
|
|
26
|
+
twoFactorEnabled: boolean("two_factor_enabled").default(false),
|
|
27
|
+
phoneNumber: text("phone_number").unique(),
|
|
28
|
+
phoneNumberVerified: boolean("phone_number_verified").default(false),
|
|
22
29
|
});
|
|
23
30
|
|
|
24
31
|
export const session = pgTable("session", {
|
|
@@ -37,25 +44,35 @@ export const session = pgTable("session", {
|
|
|
37
44
|
activeOrganizationId: text("active_organization_id"),
|
|
38
45
|
});
|
|
39
46
|
|
|
40
|
-
export const account = pgTable(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.notNull()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.notNull(),
|
|
58
|
-
|
|
47
|
+
export const account = pgTable(
|
|
48
|
+
"account",
|
|
49
|
+
{
|
|
50
|
+
id: text("id").primaryKey(),
|
|
51
|
+
issuer: text("issuer").notNull(),
|
|
52
|
+
accountId: text("account_id").notNull(),
|
|
53
|
+
providerId: text("provider_id").notNull(),
|
|
54
|
+
userId: text("user_id")
|
|
55
|
+
.notNull()
|
|
56
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
57
|
+
accessToken: text("access_token"),
|
|
58
|
+
refreshToken: text("refresh_token"),
|
|
59
|
+
idToken: text("id_token"),
|
|
60
|
+
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
61
|
+
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
62
|
+
scope: text("scope"),
|
|
63
|
+
password: text("password"),
|
|
64
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
65
|
+
updatedAt: timestamp("updated_at")
|
|
66
|
+
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
67
|
+
.notNull(),
|
|
68
|
+
},
|
|
69
|
+
(table) => [
|
|
70
|
+
uniqueIndex("account_issuer_account_id_unique").on(
|
|
71
|
+
table.issuer,
|
|
72
|
+
table.accountId,
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
);
|
|
59
76
|
|
|
60
77
|
export const verification = pgTable("verification", {
|
|
61
78
|
id: text("id").primaryKey(),
|
|
@@ -136,4 +153,18 @@ export const jwks = pgTable("jwks", {
|
|
|
136
153
|
privateKey: text("private_key").notNull(),
|
|
137
154
|
createdAt: timestamp("created_at").notNull(),
|
|
138
155
|
expiresAt: timestamp("expires_at"),
|
|
156
|
+
alg: text("alg"),
|
|
157
|
+
crv: text("crv"),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
export const twoFactor = pgTable("twoFactor", {
|
|
161
|
+
id: text("id").primaryKey(),
|
|
162
|
+
secret: text("secret").notNull(),
|
|
163
|
+
backupCodes: text("backup_codes").notNull(),
|
|
164
|
+
userId: text("user_id")
|
|
165
|
+
.notNull()
|
|
166
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
167
|
+
verified: boolean("verified").default(false),
|
|
168
|
+
failedVerificationCount: integer("failed_verification_count").default(0),
|
|
169
|
+
lockedUntil: timestamp("locked_until"),
|
|
139
170
|
});
|
package/src/auth/middleware.ts
CHANGED
|
@@ -5,7 +5,15 @@ import type { AuthInstance } from "./setup.js";
|
|
|
5
5
|
import { DEFAULT_ORG_ID } from "./org.js";
|
|
6
6
|
import { isStrictOrgResolution } from "./strict-org-resolution.js";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
function emptyToNull(value: string | null | undefined): string | null {
|
|
9
|
+
return value == null || value === "" ? null : value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Exported so the storefront contract can be pinned by a test: an anonymous
|
|
13
|
+
// visitor resolved by `storeResolver` gets these permissions, so dropping
|
|
14
|
+
// `catalog:read` here would 401 every public storefront read.
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_CUSTOMER_PERMISSIONS = [
|
|
9
17
|
"catalog:read",
|
|
10
18
|
"cart:create",
|
|
11
19
|
"cart:read",
|
|
@@ -192,9 +200,13 @@ export function authMiddleware(
|
|
|
192
200
|
: null;
|
|
193
201
|
const metaOrg = typeof meta?.organizationId === "string" ? meta.organizationId : undefined;
|
|
194
202
|
const orgId = (metaOrg ?? apiKey.organizationId ?? defaultOrgId) as string;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
// No operator and no reference means the key carries no user
|
|
204
|
+
// identity. Leave it absent rather than substituting a shared
|
|
205
|
+
// placeholder, which would make every such key look like one person.
|
|
206
|
+
const userId = emptyToNull(
|
|
207
|
+
(typeof meta?.operatorId === "string" ? meta.operatorId : undefined) ??
|
|
208
|
+
(typeof apiKey.referenceId === "string" ? apiKey.referenceId : undefined),
|
|
209
|
+
);
|
|
198
210
|
|
|
199
211
|
// Better Auth stores permissions as Record<string, string[]>
|
|
200
212
|
// (e.g. {"catalog":["read","create"]}). Flatten to the
|
|
@@ -245,7 +257,7 @@ export function authMiddleware(
|
|
|
245
257
|
// services can scope queries correctly.
|
|
246
258
|
c.set("actor", {
|
|
247
259
|
type: "user",
|
|
248
|
-
userId:
|
|
260
|
+
userId: null,
|
|
249
261
|
email: null,
|
|
250
262
|
name: "Anonymous",
|
|
251
263
|
vendorId: null,
|
package/src/auth/org.ts
CHANGED
|
@@ -37,14 +37,26 @@ export function setBootDefaultOrgId(orgId: string): void {
|
|
|
37
37
|
_bootDefaultOrgId = orgId;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Resolves org using an explicit config default — not ambient boot state.
|
|
42
|
+
* Use when the caller holds CommerceConfig and may lack an actor organizationId.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveOrgIdForCommerce(
|
|
45
|
+
actor: unknown,
|
|
46
|
+
config?: CommerceConfig | null,
|
|
47
|
+
): string {
|
|
48
|
+
return resolveOrgId(actor, config?.auth?.defaultOrganizationId, config ?? undefined);
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
/**
|
|
41
52
|
* Extracts the organization ID from an actor.
|
|
42
53
|
*
|
|
43
54
|
* Resolution order:
|
|
44
55
|
* 1. Actor's organizationId (set by middleware from session/API key/storeResolver)
|
|
45
56
|
* 2. Explicit defaultOrgId parameter (caller override)
|
|
46
|
-
* 3.
|
|
47
|
-
* 4.
|
|
57
|
+
* 3. Strict check — throw OrgResolutionError when enabled
|
|
58
|
+
* 4. Boot-time default (from config.auth.defaultOrganizationId via setBootDefaultOrgId)
|
|
59
|
+
* 5. Deprecated fallback to DEFAULT_ORG_ID (will be removed)
|
|
48
60
|
*/
|
|
49
61
|
export function resolveOrgId(
|
|
50
62
|
actor: unknown,
|
|
@@ -56,12 +68,12 @@ export function resolveOrgId(
|
|
|
56
68
|
if (typeof orgId === "string") return orgId;
|
|
57
69
|
}
|
|
58
70
|
if (defaultOrgId) return defaultOrgId;
|
|
59
|
-
if (_bootDefaultOrgId) return _bootDefaultOrgId;
|
|
60
71
|
if (isStrictOrgResolution(commerceConfig)) {
|
|
61
72
|
throw new OrgResolutionError(
|
|
62
73
|
"Organization could not be resolved: no actor organizationId, no defaultOrgId, and no configured default organization.",
|
|
63
74
|
);
|
|
64
75
|
}
|
|
76
|
+
if (_bootDefaultOrgId) return _bootDefaultOrgId;
|
|
65
77
|
warnDeprecatedOrgDefaultFallback();
|
|
66
78
|
return DEFAULT_ORG_ID;
|
|
67
79
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import { and, eq } from "drizzle-orm";
|
|
3
|
+
import type { CommerceConfig } from "../config/types.js";
|
|
4
|
+
import type { DrizzleDatabase } from "../kernel/database/drizzle-db.js";
|
|
5
|
+
import { invitation, member } from "./auth-schema.js";
|
|
6
|
+
import { canGrantRole, granterForRole, isCompositeRole } from "./role-authority.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Guards Better Auth's organization endpoints, which this repository mounts but
|
|
10
|
+
* does not govern.
|
|
11
|
+
*
|
|
12
|
+
* Red-team round 6 found two problems behind that mount.
|
|
13
|
+
*
|
|
14
|
+
* Invitation acceptance re-checked nothing: an invitation minted a member at
|
|
15
|
+
* the authority its inviter held up to seven days ago, so a demoted or revoked
|
|
16
|
+
* inviter's outstanding invitation still handed out ownership — a full
|
|
17
|
+
* organization takeover. Acceptance now re-reads the inviter's current
|
|
18
|
+
* authority before the grant is allowed through.
|
|
19
|
+
*
|
|
20
|
+
* And the plugin's own membership writers are a second role-changing surface
|
|
21
|
+
* running none of the rules `admin/staff.ts` enforces: no permission
|
|
22
|
+
* containment, no rank floor, no last-owner invariant, and they accept
|
|
23
|
+
* comma-composite role strings that `admin/staff.ts` refuses. They are refused
|
|
24
|
+
* here. `admin/staff.ts` is this repository's governed membership surface, and
|
|
25
|
+
* having one is worth more than making two of them agree.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const ORGANIZATION_PREFIX = "/api/auth/organization/";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Better Auth endpoints that write membership or define what a role may do.
|
|
32
|
+
*
|
|
33
|
+
* `invite-member` currently throws for every role instead of refusing: the
|
|
34
|
+
* plugin is configured with commerce permission arrays where its access-control
|
|
35
|
+
* model expects `Role` objects exposing `.authorize()`, so its permission check
|
|
36
|
+
* dies of type confusion. That is fail-closed by accident, and the accident is
|
|
37
|
+
* one "fix the roles wiring" commit away from being fail-open. Refusing here
|
|
38
|
+
* makes the closure deliberate, so repairing that wiring cannot quietly open an
|
|
39
|
+
* ungoverned door. `update-member-role` is not even inert today — Better Auth
|
|
40
|
+
* short-circuits it for organization creators, so an owner reaches it.
|
|
41
|
+
*/
|
|
42
|
+
const REFUSED_ENDPOINTS = new Set([
|
|
43
|
+
"invite-member",
|
|
44
|
+
"update-member-role",
|
|
45
|
+
"remove-member",
|
|
46
|
+
"leave",
|
|
47
|
+
"create-role",
|
|
48
|
+
"update-role",
|
|
49
|
+
"delete-role",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function endpointName(path: string): string | null {
|
|
53
|
+
const normalized = path.replace(/\/+$/, "");
|
|
54
|
+
return normalized.startsWith(ORGANIZATION_PREFIX)
|
|
55
|
+
? normalized.slice(ORGANIZATION_PREFIX.length)
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function readJsonBody(request: Request): Promise<Record<string, unknown>> {
|
|
60
|
+
try {
|
|
61
|
+
return (await request.clone().json()) as Record<string, unknown>;
|
|
62
|
+
} catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function organizationGuard(
|
|
68
|
+
db: DrizzleDatabase,
|
|
69
|
+
config: CommerceConfig,
|
|
70
|
+
): MiddlewareHandler {
|
|
71
|
+
return async (c, next) => {
|
|
72
|
+
if (c.req.method !== "POST") {
|
|
73
|
+
await next();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const endpoint = endpointName(c.req.path);
|
|
78
|
+
if (endpoint === null) {
|
|
79
|
+
await next();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (REFUSED_ENDPOINTS.has(endpoint)) {
|
|
84
|
+
return c.json(
|
|
85
|
+
{
|
|
86
|
+
error: {
|
|
87
|
+
code: "FORBIDDEN",
|
|
88
|
+
message: `"${endpoint}" is not a governed membership surface. Use /api/admin/staff, which enforces permission containment, the rank floor, and the last-owner invariant.`,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
403,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (endpoint !== "accept-invitation") {
|
|
96
|
+
await next();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const body = await readJsonBody(c.req.raw);
|
|
101
|
+
const invitationId = typeof body.invitationId === "string" ? body.invitationId : null;
|
|
102
|
+
if (!invitationId) {
|
|
103
|
+
await next();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rows = await db
|
|
108
|
+
.select()
|
|
109
|
+
.from(invitation)
|
|
110
|
+
.where(eq(invitation.id, invitationId));
|
|
111
|
+
const pending = rows[0];
|
|
112
|
+
// Missing, already-answered, or expired invitations are Better Auth's own
|
|
113
|
+
// error surface — do not duplicate its messages here.
|
|
114
|
+
if (!pending || pending.status !== "pending") {
|
|
115
|
+
await next();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const invitedRole = pending.role ?? "member";
|
|
120
|
+
const inviterRows = await db
|
|
121
|
+
.select({ role: member.role })
|
|
122
|
+
.from(member)
|
|
123
|
+
.where(
|
|
124
|
+
and(
|
|
125
|
+
eq(member.organizationId, pending.organizationId),
|
|
126
|
+
eq(member.userId, pending.inviterId),
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
const inviterRole = inviterRows[0]?.role;
|
|
130
|
+
|
|
131
|
+
const stillAuthorized =
|
|
132
|
+
inviterRole !== undefined &&
|
|
133
|
+
!isCompositeRole(invitedRole) &&
|
|
134
|
+
canGrantRole(config, granterForRole(config, inviterRole), invitedRole);
|
|
135
|
+
|
|
136
|
+
if (stillAuthorized) {
|
|
137
|
+
await next();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The grant is dead: burn it rather than leaving it to be retried.
|
|
142
|
+
await db
|
|
143
|
+
.update(invitation)
|
|
144
|
+
.set({ status: "canceled" })
|
|
145
|
+
.where(and(eq(invitation.id, invitationId), eq(invitation.status, "pending")));
|
|
146
|
+
|
|
147
|
+
return c.json(
|
|
148
|
+
{
|
|
149
|
+
error: {
|
|
150
|
+
code: "FORBIDDEN",
|
|
151
|
+
message:
|
|
152
|
+
"This invitation is no longer valid: the member who sent it can no longer grant that role.",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
403,
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
}
|
package/src/auth/permissions.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { CommerceForbiddenError } from "../kernel/errors.js";
|
|
2
2
|
import type { Actor } from "./types.js";
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
if (!actor)
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
if (actor.permissions.includes("*:*")) return;
|
|
4
|
+
export function hasPermission(actor: Actor | null, required: string): boolean {
|
|
5
|
+
if (!actor) return false;
|
|
6
|
+
if (actor.permissions.includes("*:*")) return true;
|
|
10
7
|
|
|
11
8
|
const [resource] = required.split(":");
|
|
12
|
-
if (resource && actor.permissions.includes(`${resource}:*`)) return;
|
|
13
|
-
|
|
9
|
+
if (resource && actor.permissions.includes(`${resource}:*`)) return true;
|
|
10
|
+
return actor.permissions.includes(required);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function assertPermission(actor: Actor | null, required: string): void {
|
|
14
|
+
if (hasPermission(actor, required)) return;
|
|
15
|
+
if (!actor) throw new CommerceForbiddenError("Authentication required.");
|
|
14
16
|
|
|
15
17
|
throw new CommerceForbiddenError(
|
|
16
18
|
`Permission "${required}" is required. Your role "${actor.role}" does not include this permission.`,
|
|
@@ -22,7 +24,28 @@ export function assertOwnership(actor: Actor | null, resourceOwnerId: string | n
|
|
|
22
24
|
throw new CommerceForbiddenError("Authentication required.");
|
|
23
25
|
}
|
|
24
26
|
if (actor.permissions.includes("*:*")) return;
|
|
27
|
+
if (!actor.userId || !resourceOwnerId) {
|
|
28
|
+
throw new CommerceForbiddenError("You do not have access to this resource.");
|
|
29
|
+
}
|
|
25
30
|
if (actor.userId !== resourceOwnerId) {
|
|
26
31
|
throw new CommerceForbiddenError("You do not have access to this resource.");
|
|
27
32
|
}
|
|
28
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The actor's user id, for code that keys a per-person resource on it.
|
|
37
|
+
*
|
|
38
|
+
* An actor without a user identity — a store resolver, an API key — has no
|
|
39
|
+
* owner key at all. Passing its absence through as one would make every such
|
|
40
|
+
* caller look like the same person, so this refuses instead of returning a
|
|
41
|
+
* value that reads as an identity.
|
|
42
|
+
*/
|
|
43
|
+
export function requireUserId(actor: Pick<Actor, "userId"> | null): string {
|
|
44
|
+
if (!actor) {
|
|
45
|
+
throw new CommerceForbiddenError("Authentication required.");
|
|
46
|
+
}
|
|
47
|
+
if (!actor.userId) {
|
|
48
|
+
throw new CommerceForbiddenError("This action requires a signed-in user.");
|
|
49
|
+
}
|
|
50
|
+
return actor.userId;
|
|
51
|
+
}
|