@classytic/arc 1.1.0 → 2.1.2
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/README.md +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
//#region src/policies/PolicyInterface.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a PermissionCheck from access control statements.
|
|
4
|
+
*
|
|
5
|
+
* Maps Better Auth's statement-based access control model to Arc's
|
|
6
|
+
* PermissionCheck function, which can be used directly in resource permissions.
|
|
7
|
+
*
|
|
8
|
+
* The returned PermissionCheck:
|
|
9
|
+
* 1. Looks up the resource + action in the statements list
|
|
10
|
+
* 2. If no matching statement exists, denies access
|
|
11
|
+
* 3. If a matching statement exists and `checkPermission` is provided,
|
|
12
|
+
* calls it for dynamic verification (e.g., check org role)
|
|
13
|
+
* 4. If `checkPermission` is not provided, allows access based on static statements
|
|
14
|
+
*
|
|
15
|
+
* @example Static statements only
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createAccessControlPolicy } from '@classytic/arc/policies';
|
|
18
|
+
*
|
|
19
|
+
* const editorPermissions = createAccessControlPolicy({
|
|
20
|
+
* statements: [
|
|
21
|
+
* { resource: 'product', action: ['create', 'update'] },
|
|
22
|
+
* { resource: 'order', action: ['read'] },
|
|
23
|
+
* ],
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Use in resource config
|
|
27
|
+
* defineResource({
|
|
28
|
+
* name: 'product',
|
|
29
|
+
* permissions: {
|
|
30
|
+
* create: editorPermissions,
|
|
31
|
+
* update: editorPermissions,
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example With dynamic permission check (Better Auth org roles)
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const policy = createAccessControlPolicy({
|
|
39
|
+
* statements: [
|
|
40
|
+
* { resource: 'product', action: ['create', 'update'] },
|
|
41
|
+
* { resource: 'order', action: ['read'] },
|
|
42
|
+
* ],
|
|
43
|
+
* checkPermission: async (userId, resource, action) => {
|
|
44
|
+
* return hasOrgPermission(userId, resource, action);
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
function createAccessControlPolicy(options) {
|
|
50
|
+
const statementMap = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const statement of options.statements) {
|
|
52
|
+
const existing = statementMap.get(statement.resource);
|
|
53
|
+
if (existing) for (const action of statement.action) existing.add(action);
|
|
54
|
+
else statementMap.set(statement.resource, new Set(statement.action));
|
|
55
|
+
}
|
|
56
|
+
const permissionCheck = async (context) => {
|
|
57
|
+
const { user, resource, action } = context;
|
|
58
|
+
const allowedActions = statementMap.get(resource);
|
|
59
|
+
if (!allowedActions || !allowedActions.has(action)) return {
|
|
60
|
+
granted: false,
|
|
61
|
+
reason: `Action '${action}' is not permitted on resource '${resource}'`
|
|
62
|
+
};
|
|
63
|
+
if (options.checkPermission) {
|
|
64
|
+
const userId = user?.id ?? user?._id;
|
|
65
|
+
if (!userId) return {
|
|
66
|
+
granted: false,
|
|
67
|
+
reason: "Authentication required"
|
|
68
|
+
};
|
|
69
|
+
if (!await options.checkPermission(String(userId), resource, action)) return {
|
|
70
|
+
granted: false,
|
|
71
|
+
reason: `User does not have '${action}' permission on '${resource}'`
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { granted: true };
|
|
75
|
+
};
|
|
76
|
+
return permissionCheck;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/policies/helpers.ts
|
|
81
|
+
/**
|
|
82
|
+
* Helper to create Fastify middleware from any PolicyEngine implementation
|
|
83
|
+
*
|
|
84
|
+
* This is a convenience function that provides a standard middleware pattern.
|
|
85
|
+
* Most policies can use this instead of implementing toMiddleware() manually.
|
|
86
|
+
*
|
|
87
|
+
* @param policy - Policy engine instance
|
|
88
|
+
* @param operation - Operation name (list, get, create, update, delete)
|
|
89
|
+
* @returns Fastify preHandler middleware
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* class SimplePolicy implements PolicyEngine {
|
|
94
|
+
* can(user, operation) {
|
|
95
|
+
* return { allowed: user.isActive };
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* toMiddleware(operation) {
|
|
99
|
+
* return createPolicyMiddleware(this, operation);
|
|
100
|
+
* }
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
function createPolicyMiddleware(policy, operation) {
|
|
105
|
+
return async function policyMiddleware(request, reply) {
|
|
106
|
+
const context = {
|
|
107
|
+
document: request.document,
|
|
108
|
+
body: request.body,
|
|
109
|
+
params: request.params,
|
|
110
|
+
query: request.query
|
|
111
|
+
};
|
|
112
|
+
const result = await policy.can(request.user, operation, context);
|
|
113
|
+
if (!result.allowed) return reply.code(403).send({
|
|
114
|
+
success: false,
|
|
115
|
+
error: "Access denied",
|
|
116
|
+
message: result.reason || "You do not have permission to perform this action"
|
|
117
|
+
});
|
|
118
|
+
request.policyResult = result;
|
|
119
|
+
if (result.filters && Object.keys(result.filters).length > 0) request._policyFilters = result.filters;
|
|
120
|
+
if (result.fieldMask) request.fieldMask = result.fieldMask;
|
|
121
|
+
if (result.metadata) request.policyMetadata = result.metadata;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Combine multiple policies with AND logic
|
|
126
|
+
*
|
|
127
|
+
* All policies must allow the operation for it to succeed.
|
|
128
|
+
* First denial stops evaluation and returns the denial reason.
|
|
129
|
+
*
|
|
130
|
+
* @param policies - Array of policy engines to combine
|
|
131
|
+
* @returns Combined policy engine
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const combinedPolicy = combinePolicies(
|
|
136
|
+
* rbacPolicy, // Must have correct role
|
|
137
|
+
* ownershipPolicy, // Must own the resource
|
|
138
|
+
* auditPolicy, // Logs access for compliance
|
|
139
|
+
* );
|
|
140
|
+
*
|
|
141
|
+
* // All three policies must pass for the operation to succeed
|
|
142
|
+
* const result = await combinedPolicy.can(user, 'update', context);
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example Multi-tenant + RBAC
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const policy = combinePolicies(
|
|
148
|
+
* definePolicy({ tenant: { field: 'organizationId' } }),
|
|
149
|
+
* definePolicy({ roles: { update: ['admin', 'editor'] } }),
|
|
150
|
+
* );
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
function combinePolicies(...policies) {
|
|
154
|
+
if (policies.length === 0) throw new Error("combinePolicies requires at least one policy");
|
|
155
|
+
if (policies.length === 1) return policies[0];
|
|
156
|
+
return {
|
|
157
|
+
async can(user, operation, context) {
|
|
158
|
+
const results = [];
|
|
159
|
+
for (const policy of policies) {
|
|
160
|
+
const result = await policy.can(user, operation, context);
|
|
161
|
+
if (!result.allowed) return result;
|
|
162
|
+
results.push(result);
|
|
163
|
+
}
|
|
164
|
+
const mergedResult = {
|
|
165
|
+
allowed: true,
|
|
166
|
+
filters: {},
|
|
167
|
+
metadata: {}
|
|
168
|
+
};
|
|
169
|
+
for (const result of results) if (result.filters) Object.assign(mergedResult.filters, result.filters);
|
|
170
|
+
const allExcludes = /* @__PURE__ */ new Set();
|
|
171
|
+
const allIncludes = [];
|
|
172
|
+
for (const result of results) {
|
|
173
|
+
if (result.fieldMask?.exclude) result.fieldMask.exclude.forEach((field) => allExcludes.add(field));
|
|
174
|
+
if (result.fieldMask?.include) allIncludes.push(new Set(result.fieldMask.include));
|
|
175
|
+
}
|
|
176
|
+
if (allExcludes.size > 0 || allIncludes.length > 0) {
|
|
177
|
+
mergedResult.fieldMask = {};
|
|
178
|
+
if (allExcludes.size > 0) mergedResult.fieldMask.exclude = Array.from(allExcludes);
|
|
179
|
+
if (allIncludes.length > 0) {
|
|
180
|
+
const intersection = allIncludes.reduce((acc, set) => {
|
|
181
|
+
return new Set([...acc].filter((x) => set.has(x)));
|
|
182
|
+
});
|
|
183
|
+
if (intersection.size > 0) mergedResult.fieldMask.include = Array.from(intersection);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const result of results) if (result.metadata) Object.assign(mergedResult.metadata, result.metadata);
|
|
187
|
+
if (Object.keys(mergedResult.filters).length === 0) delete mergedResult.filters;
|
|
188
|
+
if (Object.keys(mergedResult.metadata).length === 0) delete mergedResult.metadata;
|
|
189
|
+
return mergedResult;
|
|
190
|
+
},
|
|
191
|
+
toMiddleware(operation) {
|
|
192
|
+
const middlewares = policies.map((p) => p.toMiddleware(operation));
|
|
193
|
+
return async (request, reply) => {
|
|
194
|
+
for (const middleware of middlewares) {
|
|
195
|
+
await middleware(request, reply);
|
|
196
|
+
if (reply.sent) return;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Combine multiple policies with OR logic
|
|
204
|
+
*
|
|
205
|
+
* At least one policy must allow the operation for it to succeed.
|
|
206
|
+
* If all policies deny, returns the first denial reason.
|
|
207
|
+
*
|
|
208
|
+
* @param policies - Array of policy engines to combine
|
|
209
|
+
* @returns Combined policy engine
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* const policy = anyPolicy(
|
|
214
|
+
* ownerPolicy, // User owns the resource
|
|
215
|
+
* adminPolicy, // OR user is admin
|
|
216
|
+
* publicPolicy, // OR resource is public
|
|
217
|
+
* );
|
|
218
|
+
*
|
|
219
|
+
* // Any one of these policies passing allows the operation
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
function anyPolicy(...policies) {
|
|
223
|
+
if (policies.length === 0) throw new Error("anyPolicy requires at least one policy");
|
|
224
|
+
if (policies.length === 1) return policies[0];
|
|
225
|
+
return {
|
|
226
|
+
async can(user, operation, context) {
|
|
227
|
+
let firstDenial = null;
|
|
228
|
+
for (const policy of policies) {
|
|
229
|
+
const result = await policy.can(user, operation, context);
|
|
230
|
+
if (result.allowed) return result;
|
|
231
|
+
if (!firstDenial) firstDenial = result;
|
|
232
|
+
}
|
|
233
|
+
return firstDenial;
|
|
234
|
+
},
|
|
235
|
+
toMiddleware(operation) {
|
|
236
|
+
return async (request, reply) => {
|
|
237
|
+
const results = [];
|
|
238
|
+
for (const policy of policies) {
|
|
239
|
+
const result = await policy.can(request.user, operation, {
|
|
240
|
+
document: request.document,
|
|
241
|
+
body: request.body,
|
|
242
|
+
params: request.params,
|
|
243
|
+
query: request.query
|
|
244
|
+
});
|
|
245
|
+
if (result.allowed) {
|
|
246
|
+
request.policyResult = result;
|
|
247
|
+
if (result.filters) request._policyFilters = result.filters;
|
|
248
|
+
if (result.fieldMask) request.fieldMask = result.fieldMask;
|
|
249
|
+
if (result.metadata) request.policyMetadata = result.metadata;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
results.push(result);
|
|
253
|
+
}
|
|
254
|
+
return reply.code(403).send({
|
|
255
|
+
success: false,
|
|
256
|
+
error: "Access denied",
|
|
257
|
+
message: results[0]?.reason || "You do not have permission to perform this action"
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a pass-through policy that always allows
|
|
265
|
+
*
|
|
266
|
+
* Useful for testing or for routes that don't need authorization.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* const policy = allowAll();
|
|
271
|
+
* const result = await policy.can(user, 'any-operation');
|
|
272
|
+
* // result.allowed === true
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
function allowAll() {
|
|
276
|
+
return {
|
|
277
|
+
can() {
|
|
278
|
+
return { allowed: true };
|
|
279
|
+
},
|
|
280
|
+
toMiddleware() {
|
|
281
|
+
return async () => {};
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Create a policy that always denies
|
|
287
|
+
*
|
|
288
|
+
* Useful for explicitly blocking operations or for testing.
|
|
289
|
+
*
|
|
290
|
+
* @param reason - Denial reason
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* const policy = denyAll('This resource is deprecated');
|
|
295
|
+
* const result = await policy.can(user, 'any-operation');
|
|
296
|
+
* // result.allowed === false
|
|
297
|
+
* // result.reason === 'This resource is deprecated'
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
function denyAll(reason = "Operation not allowed") {
|
|
301
|
+
return {
|
|
302
|
+
can() {
|
|
303
|
+
return {
|
|
304
|
+
allowed: false,
|
|
305
|
+
reason
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
toMiddleware() {
|
|
309
|
+
return async (request, reply) => {
|
|
310
|
+
return reply.code(403).send({
|
|
311
|
+
success: false,
|
|
312
|
+
error: "Access denied",
|
|
313
|
+
message: reason
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
export { allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };
|
|
322
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/policies/PolicyInterface.ts","../../src/policies/helpers.ts"],"sourcesContent":["/**\n * Policy Interface\n *\n * Pluggable authorization interface for Arc.\n * Apps implement this interface to define custom authorization strategies.\n *\n * @example RBAC Policy\n * ```typescript\n * class RBACPolicy implements PolicyEngine {\n * can(user, operation, context) {\n * return {\n * allowed: user.roles.includes('admin'),\n * reason: 'Admin role required',\n * };\n * }\n * toMiddleware(operation) {\n * return async (request, reply) => {\n * const result = await this.can(request.user, operation);\n * if (!result.allowed) {\n * reply.code(403).send({ error: result.reason });\n * }\n * };\n * }\n * }\n * ```\n *\n * @example ABAC (Attribute-Based) Policy\n * ```typescript\n * class ABACPolicy implements PolicyEngine {\n * can(user, operation, context) {\n * return {\n * allowed: this.evaluateAttributes(user, operation, context),\n * filters: { department: user.department },\n * fieldMask: { exclude: ['salary', 'ssn'] },\n * };\n * }\n * // ...\n * }\n * ```\n */\n\nimport type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { PermissionCheck } from '../permissions/types.js';\n\n/**\n * Policy result returned by can() method\n */\nexport interface PolicyResult {\n /**\n * Whether the operation is allowed\n */\n allowed: boolean;\n\n /**\n * Human-readable reason if denied\n * Returned in 403 error responses\n */\n reason?: string;\n\n /**\n * Query filters to apply (for list operations)\n *\n * @example\n * ```typescript\n * // Multi-tenant filter\n * { organizationId: user.organizationId }\n *\n * // Ownership filter\n * { userId: user.id }\n *\n * // Complex filter\n * { $or: [{ public: true }, { createdBy: user.id }] }\n * ```\n */\n filters?: Record<string, any>;\n\n /**\n * Fields to include/exclude in response\n *\n * @example\n * ```typescript\n * // Hide sensitive fields from non-admins\n * { exclude: ['password', 'ssn', 'salary'] }\n *\n * // Only show specific fields\n * { include: ['name', 'email', 'role'] }\n * ```\n */\n fieldMask?: {\n include?: string[];\n exclude?: string[];\n };\n\n /**\n * Additional context for downstream middleware\n *\n * @example\n * ```typescript\n * {\n * auditLog: { action: 'read', resource: 'patient', userId: user.id },\n * rateLimit: { tier: user.subscriptionTier },\n * }\n * ```\n */\n metadata?: Record<string, any>;\n}\n\n/**\n * Policy context provided to can() method\n */\nexport interface PolicyContext {\n /**\n * The document being accessed (for update/delete/get)\n * Populated by fetchDocument middleware\n */\n document?: any;\n\n /**\n * Request body (for create/update)\n */\n body?: any;\n\n /**\n * Request params (e.g., :id from route)\n */\n params?: any;\n\n /**\n * Request query parameters\n */\n query?: any;\n\n /**\n * Additional app-specific context\n * Can include anything your policy needs to make decisions\n */\n [key: string]: any;\n}\n\n/**\n * Policy Engine Interface\n *\n * Implement this interface to create your own authorization strategy.\n *\n * Arc provides the interface, apps provide the implementation.\n * This follows the same pattern as:\n * - Database drivers (interface: query(), implementation: PostgreSQL, MySQL)\n * - Storage providers (interface: upload(), implementation: S3, Azure)\n * - Authentication strategies (interface: verify(), implementation: JWT, OAuth)\n *\n * @example E-commerce RBAC + Ownership\n * ```typescript\n * class EcommercePolicyEngine implements PolicyEngine {\n * constructor(private config: { roles: Record<string, string[]> }) {}\n *\n * can(user, operation, context) {\n * // Check RBAC\n * const allowedRoles = this.config.roles[operation] || [];\n * if (!user.roles.some(r => allowedRoles.includes(r))) {\n * return { allowed: false, reason: 'Insufficient permissions' };\n * }\n *\n * // Check ownership for update/delete\n * if (['update', 'delete'].includes(operation)) {\n * if (context.document.userId !== user.id) {\n * return { allowed: false, reason: 'Not the owner' };\n * }\n * }\n *\n * // Multi-tenant filter for list\n * if (operation === 'list') {\n * return {\n * allowed: true,\n * filters: { organizationId: user.organizationId },\n * };\n * }\n *\n * return { allowed: true };\n * }\n *\n * toMiddleware(operation) {\n * return async (request, reply) => {\n * const result = await this.can(request.user, operation, {\n * document: request.document,\n * body: request.body,\n * params: request.params,\n * query: request.query,\n * });\n *\n * if (!result.allowed) {\n * reply.code(403).send({ error: result.reason });\n * }\n *\n * // Attach filters/fieldMask to request\n * request.policyResult = result;\n * };\n * }\n * }\n * ```\n *\n * @example HIPAA Compliance\n * ```typescript\n * class HIPAAPolicyEngine implements PolicyEngine {\n * can(user, operation, context) {\n * // Check patient consent\n * // Verify user certifications\n * // Check data sensitivity level\n * // Create audit log entry\n *\n * return {\n * allowed: this.checkHIPAACompliance(user, operation, context),\n * reason: 'HIPAA compliance check failed',\n * metadata: {\n * auditLog: this.createAuditEntry(user, operation),\n * },\n * };\n * }\n *\n * toMiddleware(operation) {\n * // HIPAA-specific middleware with audit logging\n * }\n * }\n * ```\n */\nexport interface PolicyEngine {\n /**\n * Check if user can perform operation\n *\n * @param user - User object from request (request.user)\n * @param operation - Operation name (list, get, create, update, delete, custom)\n * @param context - Additional context (document, body, params, query, etc.)\n * @returns Policy result with allowed/denied and optional filters/fieldMask\n *\n * @example\n * ```typescript\n * const result = await policy.can(request.user, 'update', {\n * document: existingDocument,\n * body: request.body,\n * });\n *\n * if (!result.allowed) {\n * throw new Error(result.reason);\n * }\n * ```\n */\n can(\n user: any,\n operation: string,\n context?: PolicyContext\n ): PolicyResult | Promise<PolicyResult>;\n\n /**\n * Generate Fastify middleware for this policy\n *\n * Called during route registration to create preHandler middleware.\n * Middleware should:\n * 1. Call can() with request context\n * 2. Return 403 if denied\n * 3. Attach result to request for downstream use\n *\n * @param operation - Operation name (list, get, create, update, delete)\n * @returns Fastify preHandler middleware\n *\n * @example\n * ```typescript\n * const middleware = policy.toMiddleware('update');\n * fastify.put('/products/:id', {\n * preHandler: [authenticate, middleware],\n * }, handler);\n * ```\n */\n toMiddleware(\n operation: string\n ): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;\n}\n\n/**\n * Policy factory function signature\n *\n * Policies are typically created via factory functions that accept configuration.\n *\n * @example\n * ```typescript\n * export function definePolicy(config: PolicyConfig): PolicyEngine {\n * return new MyPolicyEngine(config);\n * }\n *\n * // Usage\n * const productPolicy = definePolicy({\n * resource: 'product',\n * roles: { list: ['user'], create: ['admin'] },\n * ownership: { field: 'userId', operations: ['update', 'delete'] },\n * });\n * ```\n */\nexport type PolicyFactory<TConfig = any> = (config: TConfig) => PolicyEngine;\n\n/**\n * Extended Fastify request with policy result\n */\n/**\n * Access control statement\n *\n * Maps to Better Auth's organization permission model\n * where permissions are defined as resource + action pairs.\n */\nexport interface AccessControlStatement {\n /** Resource name (e.g., 'product', 'order') */\n resource: string;\n /** Allowed actions on this resource */\n action: string[];\n}\n\n/**\n * Options for createAccessControlPolicy\n */\nexport interface AccessControlPolicyOptions {\n /** Permission statements defining resource-action pairs */\n statements: AccessControlStatement[];\n /**\n * Optional async permission check against external source (e.g., org role permissions).\n * Called when the static statements allow the action — use this for dynamic checks\n * like verifying the user's org role actually grants the permission.\n *\n * @param userId - ID of the user\n * @param resource - Resource being accessed\n * @param action - Action being performed\n * @returns Whether the user has the permission\n */\n checkPermission?: (userId: string, resource: string, action: string) => Promise<boolean>;\n}\n\n/**\n * Create a PermissionCheck from access control statements.\n *\n * Maps Better Auth's statement-based access control model to Arc's\n * PermissionCheck function, which can be used directly in resource permissions.\n *\n * The returned PermissionCheck:\n * 1. Looks up the resource + action in the statements list\n * 2. If no matching statement exists, denies access\n * 3. If a matching statement exists and `checkPermission` is provided,\n * calls it for dynamic verification (e.g., check org role)\n * 4. If `checkPermission` is not provided, allows access based on static statements\n *\n * @example Static statements only\n * ```typescript\n * import { createAccessControlPolicy } from '@classytic/arc/policies';\n *\n * const editorPermissions = createAccessControlPolicy({\n * statements: [\n * { resource: 'product', action: ['create', 'update'] },\n * { resource: 'order', action: ['read'] },\n * ],\n * });\n *\n * // Use in resource config\n * defineResource({\n * name: 'product',\n * permissions: {\n * create: editorPermissions,\n * update: editorPermissions,\n * },\n * });\n * ```\n *\n * @example With dynamic permission check (Better Auth org roles)\n * ```typescript\n * const policy = createAccessControlPolicy({\n * statements: [\n * { resource: 'product', action: ['create', 'update'] },\n * { resource: 'order', action: ['read'] },\n * ],\n * checkPermission: async (userId, resource, action) => {\n * return hasOrgPermission(userId, resource, action);\n * },\n * });\n * ```\n */\nexport function createAccessControlPolicy(\n options: AccessControlPolicyOptions\n): PermissionCheck {\n // Pre-compute a lookup map: resource -> Set<action> for O(1) checks\n const statementMap = new Map<string, Set<string>>();\n for (const statement of options.statements) {\n const existing = statementMap.get(statement.resource);\n if (existing) {\n for (const action of statement.action) {\n existing.add(action);\n }\n } else {\n statementMap.set(statement.resource, new Set(statement.action));\n }\n }\n\n const permissionCheck: PermissionCheck = async (context) => {\n const { user, resource, action } = context;\n\n // Check if the action is allowed by any statement\n const allowedActions = statementMap.get(resource);\n if (!allowedActions || !allowedActions.has(action)) {\n return {\n granted: false,\n reason: `Action '${action}' is not permitted on resource '${resource}'`,\n };\n }\n\n // If a dynamic permission check is provided, verify against it\n if (options.checkPermission) {\n const userId = user?.id ?? user?._id;\n if (!userId) {\n return {\n granted: false,\n reason: 'Authentication required',\n };\n }\n\n const hasPermission = await options.checkPermission(String(userId), resource, action);\n if (!hasPermission) {\n return {\n granted: false,\n reason: `User does not have '${action}' permission on '${resource}'`,\n };\n }\n }\n\n return { granted: true };\n };\n\n return permissionCheck;\n}\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n policyResult?: PolicyResult;\n }\n}\n","/**\n * Policy Helper Utilities\n *\n * Common operations for working with PolicyEngine implementations.\n */\n\nimport type { FastifyRequest, FastifyReply } from 'fastify';\nimport type { PolicyEngine, PolicyResult } from './PolicyInterface.js';\n\n/**\n * Helper to create Fastify middleware from any PolicyEngine implementation\n *\n * This is a convenience function that provides a standard middleware pattern.\n * Most policies can use this instead of implementing toMiddleware() manually.\n *\n * @param policy - Policy engine instance\n * @param operation - Operation name (list, get, create, update, delete)\n * @returns Fastify preHandler middleware\n *\n * @example\n * ```typescript\n * class SimplePolicy implements PolicyEngine {\n * can(user, operation) {\n * return { allowed: user.isActive };\n * }\n *\n * toMiddleware(operation) {\n * return createPolicyMiddleware(this, operation);\n * }\n * }\n * ```\n */\nexport function createPolicyMiddleware(\n policy: PolicyEngine,\n operation: string\n): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {\n return async function policyMiddleware(\n request: FastifyRequest,\n reply: FastifyReply\n ): Promise<void> {\n // Build context from request\n const context = {\n document: request.document,\n body: request.body,\n params: request.params,\n query: request.query,\n };\n\n // Check policy\n const result = await policy.can(request.user, operation, context);\n\n if (!result.allowed) {\n return reply.code(403).send({\n success: false,\n error: 'Access denied',\n message: result.reason || 'You do not have permission to perform this action',\n });\n }\n\n // Attach result to request for downstream use\n request.policyResult = result;\n\n // Apply policy filters on trusted location (for list operations)\n if (result.filters && Object.keys(result.filters).length > 0) {\n request._policyFilters = result.filters;\n }\n\n // Apply field mask (for response serialization)\n if (result.fieldMask) {\n request.fieldMask = result.fieldMask;\n }\n\n // Attach metadata (for downstream middleware/logging)\n if (result.metadata) {\n request.policyMetadata = result.metadata;\n }\n };\n}\n\n/**\n * Combine multiple policies with AND logic\n *\n * All policies must allow the operation for it to succeed.\n * First denial stops evaluation and returns the denial reason.\n *\n * @param policies - Array of policy engines to combine\n * @returns Combined policy engine\n *\n * @example\n * ```typescript\n * const combinedPolicy = combinePolicies(\n * rbacPolicy, // Must have correct role\n * ownershipPolicy, // Must own the resource\n * auditPolicy, // Logs access for compliance\n * );\n *\n * // All three policies must pass for the operation to succeed\n * const result = await combinedPolicy.can(user, 'update', context);\n * ```\n *\n * @example Multi-tenant + RBAC\n * ```typescript\n * const policy = combinePolicies(\n * definePolicy({ tenant: { field: 'organizationId' } }),\n * definePolicy({ roles: { update: ['admin', 'editor'] } }),\n * );\n * ```\n */\nexport function combinePolicies(...policies: PolicyEngine[]): PolicyEngine {\n if (policies.length === 0) {\n throw new Error('combinePolicies requires at least one policy');\n }\n\n if (policies.length === 1) {\n return policies[0]!;\n }\n\n return {\n async can(user: any, operation: string, context?: any): Promise<PolicyResult> {\n const results: PolicyResult[] = [];\n\n for (const policy of policies) {\n const result = await policy.can(user, operation, context);\n\n if (!result.allowed) {\n // First denial stops evaluation\n return result;\n }\n\n results.push(result);\n }\n\n // Merge all results\n const mergedResult: PolicyResult = {\n allowed: true,\n filters: {},\n metadata: {},\n };\n\n // Merge filters (AND logic - all filters must match)\n for (const result of results) {\n if (result.filters) {\n Object.assign(mergedResult.filters!, result.filters);\n }\n }\n\n // Merge field masks (union of excludes, intersection of includes)\n const allExcludes = new Set<string>();\n const allIncludes: Set<string>[] = [];\n\n for (const result of results) {\n if (result.fieldMask?.exclude) {\n result.fieldMask.exclude.forEach((field) => allExcludes.add(field));\n }\n if (result.fieldMask?.include) {\n allIncludes.push(new Set(result.fieldMask.include));\n }\n }\n\n if (allExcludes.size > 0 || allIncludes.length > 0) {\n mergedResult.fieldMask = {};\n\n if (allExcludes.size > 0) {\n mergedResult.fieldMask.exclude = Array.from(allExcludes);\n }\n\n if (allIncludes.length > 0) {\n // Intersection of all includes\n const intersection = allIncludes.reduce((acc, set) => {\n return new Set([...acc].filter((x) => set.has(x)));\n });\n if (intersection.size > 0) {\n mergedResult.fieldMask.include = Array.from(intersection);\n }\n }\n }\n\n // Merge metadata\n for (const result of results) {\n if (result.metadata) {\n Object.assign(mergedResult.metadata!, result.metadata);\n }\n }\n\n // Clean up empty objects\n if (Object.keys(mergedResult.filters!).length === 0) {\n delete mergedResult.filters;\n }\n if (Object.keys(mergedResult.metadata!).length === 0) {\n delete mergedResult.metadata;\n }\n\n return mergedResult;\n },\n\n toMiddleware(operation: string) {\n const middlewares = policies.map((p) => p.toMiddleware(operation));\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n for (const middleware of middlewares) {\n await middleware(request, reply);\n\n // Stop if response was sent (denial)\n if (reply.sent) {\n return;\n }\n }\n };\n },\n };\n}\n\n/**\n * Combine multiple policies with OR logic\n *\n * At least one policy must allow the operation for it to succeed.\n * If all policies deny, returns the first denial reason.\n *\n * @param policies - Array of policy engines to combine\n * @returns Combined policy engine\n *\n * @example\n * ```typescript\n * const policy = anyPolicy(\n * ownerPolicy, // User owns the resource\n * adminPolicy, // OR user is admin\n * publicPolicy, // OR resource is public\n * );\n *\n * // Any one of these policies passing allows the operation\n * ```\n */\nexport function anyPolicy(...policies: PolicyEngine[]): PolicyEngine {\n if (policies.length === 0) {\n throw new Error('anyPolicy requires at least one policy');\n }\n\n if (policies.length === 1) {\n return policies[0]!;\n }\n\n return {\n async can(user: any, operation: string, context?: any): Promise<PolicyResult> {\n let firstDenial: PolicyResult | null = null;\n\n for (const policy of policies) {\n const result = await policy.can(user, operation, context);\n\n if (result.allowed) {\n // First success stops evaluation\n return result;\n }\n\n if (!firstDenial) {\n firstDenial = result;\n }\n }\n\n // All policies denied - return first denial\n return firstDenial!;\n },\n\n toMiddleware(operation: string) {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n const results: PolicyResult[] = [];\n\n for (const policy of policies) {\n const result = await policy.can(\n request.user,\n operation,\n {\n document: request.document,\n body: request.body,\n params: request.params,\n query: request.query,\n }\n );\n\n if (result.allowed) {\n // First success - attach result and continue\n request.policyResult = result;\n\n if (result.filters) {\n request._policyFilters = result.filters;\n }\n\n if (result.fieldMask) {\n request.fieldMask = result.fieldMask;\n }\n\n if (result.metadata) {\n request.policyMetadata = result.metadata;\n }\n\n return;\n }\n\n results.push(result);\n }\n\n // All policies denied\n return reply.code(403).send({\n success: false,\n error: 'Access denied',\n message: results[0]?.reason || 'You do not have permission to perform this action',\n });\n };\n },\n };\n}\n\n/**\n * Create a pass-through policy that always allows\n *\n * Useful for testing or for routes that don't need authorization.\n *\n * @example\n * ```typescript\n * const policy = allowAll();\n * const result = await policy.can(user, 'any-operation');\n * // result.allowed === true\n * ```\n */\nexport function allowAll(): PolicyEngine {\n return {\n can() {\n return { allowed: true };\n },\n\n toMiddleware() {\n return async () => {\n // No-op - always allow\n };\n },\n };\n}\n\n/**\n * Create a policy that always denies\n *\n * Useful for explicitly blocking operations or for testing.\n *\n * @param reason - Denial reason\n *\n * @example\n * ```typescript\n * const policy = denyAll('This resource is deprecated');\n * const result = await policy.can(user, 'any-operation');\n * // result.allowed === false\n * // result.reason === 'This resource is deprecated'\n * ```\n */\nexport function denyAll(reason = 'Operation not allowed'): PolicyEngine {\n return {\n can() {\n return { allowed: false, reason };\n },\n\n toMiddleware() {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n return reply.code(403).send({\n success: false,\n error: 'Access denied',\n message: reason,\n });\n };\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,115 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import '../types-B99TBmFV.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Soft Delete Preset
|
|
10
|
-
*
|
|
11
|
-
* Adds routes for listing deleted items and restoring them.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
interface SoftDeleteOptions {
|
|
15
|
-
deletedField?: string;
|
|
16
|
-
}
|
|
17
|
-
declare function softDeletePreset(options?: SoftDeleteOptions): PresetResult;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Slug Lookup Preset
|
|
21
|
-
*
|
|
22
|
-
* Adds a route to get resource by slug.
|
|
23
|
-
*/
|
|
1
|
+
import "../elevation-B_2dRLVP.mjs";
|
|
2
|
+
import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-Ch8HU9uM.mjs";
|
|
3
|
+
import "../types-aYB4V7uN.mjs";
|
|
4
|
+
import { AnyRecord, PresetResult, ResourceConfig } from "../types/index.mjs";
|
|
5
|
+
import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
|
|
24
6
|
|
|
7
|
+
//#region src/presets/softDelete.d.ts
|
|
8
|
+
declare function softDeletePreset(): PresetResult;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/presets/slugLookup.d.ts
|
|
25
11
|
interface SlugLookupOptions {
|
|
26
|
-
|
|
12
|
+
slugField?: string;
|
|
27
13
|
}
|
|
28
14
|
declare function slugLookupPreset(options?: SlugLookupOptions): PresetResult;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Owned By User Preset
|
|
32
|
-
*
|
|
33
|
-
* Adds ownership validation for update/delete operations.
|
|
34
|
-
*
|
|
35
|
-
* BEHAVIOR:
|
|
36
|
-
* - On update/remove, sets _ownershipCheck on request
|
|
37
|
-
* - BaseController enforces ownership before mutation
|
|
38
|
-
* - Users can only modify resources where ownerField matches their ID
|
|
39
|
-
*
|
|
40
|
-
* BYPASS:
|
|
41
|
-
* - Users with bypassRoles (default: ['admin', 'superadmin']) skip check
|
|
42
|
-
* - Resources without the ownerField are not checked
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* defineResource({
|
|
46
|
-
* name: 'post',
|
|
47
|
-
* presets: [{ name: 'ownedByUser', ownerField: 'authorId' }],
|
|
48
|
-
* });
|
|
49
|
-
*
|
|
50
|
-
* // User A cannot update/delete User B's posts
|
|
51
|
-
* // Admins can modify any post
|
|
52
|
-
*/
|
|
53
|
-
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/presets/ownedByUser.d.ts
|
|
54
17
|
interface OwnedByUserOptions {
|
|
55
|
-
|
|
56
|
-
bypassRoles?: string[];
|
|
18
|
+
ownerField?: string;
|
|
57
19
|
}
|
|
58
20
|
declare function ownedByUserPreset(options?: OwnedByUserOptions): PresetResult;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* Tree Preset
|
|
62
|
-
*
|
|
63
|
-
* Adds routes for hierarchical tree structures.
|
|
64
|
-
*/
|
|
65
|
-
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/presets/tree.d.ts
|
|
66
23
|
interface TreeOptions {
|
|
67
|
-
|
|
24
|
+
parentField?: string;
|
|
68
25
|
}
|
|
69
26
|
declare function treePreset(options?: TreeOptions): PresetResult;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* Audited Preset
|
|
73
|
-
*
|
|
74
|
-
* Adds createdBy/updatedBy tracking to resources.
|
|
75
|
-
* Works with the audit plugin for full change tracking.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* defineResource({
|
|
79
|
-
* name: 'product',
|
|
80
|
-
* presets: ['audited'],
|
|
81
|
-
* // Fields createdBy, updatedBy auto-populated from user context
|
|
82
|
-
* });
|
|
83
|
-
*/
|
|
84
|
-
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/presets/audited.d.ts
|
|
85
29
|
interface AuditedPresetOptions {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
30
|
+
/** Field name for creator (default: 'createdBy') */
|
|
31
|
+
createdByField?: string;
|
|
32
|
+
/** Field name for updater (default: 'updatedBy') */
|
|
33
|
+
updatedByField?: string;
|
|
90
34
|
}
|
|
91
35
|
/**
|
|
92
36
|
* Audited preset - adds createdBy/updatedBy tracking
|
|
93
37
|
*/
|
|
94
38
|
declare function auditedPreset(options?: AuditedPresetOptions): PresetResult;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
* Preset Type Interfaces
|
|
98
|
-
*
|
|
99
|
-
* TypeScript interfaces that document the controller methods required by each preset.
|
|
100
|
-
* These interfaces help with type safety when using presets.
|
|
101
|
-
*
|
|
102
|
-
* @example Using with custom controllers
|
|
103
|
-
* ```typescript
|
|
104
|
-
* import { BaseController } from '@classytic/arc';
|
|
105
|
-
* import type { ISoftDeleteController } from '@classytic/arc/presets';
|
|
106
|
-
*
|
|
107
|
-
* class ProductController extends BaseController<Product> implements ISoftDeleteController {
|
|
108
|
-
* // TypeScript now ensures you have getDeleted() and restore() methods
|
|
109
|
-
* }
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/presets/types.d.ts
|
|
113
41
|
/**
|
|
114
42
|
* Soft Delete Preset Interface
|
|
115
43
|
*
|
|
@@ -138,16 +66,16 @@ declare function auditedPreset(options?: AuditedPresetOptions): PresetResult;
|
|
|
138
66
|
* ```
|
|
139
67
|
*/
|
|
140
68
|
interface ISoftDeleteController<TDoc = unknown> {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Get all soft-deleted items
|
|
71
|
+
* Called by: GET /deleted
|
|
72
|
+
*/
|
|
73
|
+
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginatedResult<TDoc>>>;
|
|
74
|
+
/**
|
|
75
|
+
* Restore a soft-deleted item by ID
|
|
76
|
+
* Called by: POST /:id/restore
|
|
77
|
+
*/
|
|
78
|
+
restore(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
151
79
|
}
|
|
152
80
|
/**
|
|
153
81
|
* Slug Lookup Preset Interface
|
|
@@ -175,11 +103,11 @@ interface ISoftDeleteController<TDoc = unknown> {
|
|
|
175
103
|
* ```
|
|
176
104
|
*/
|
|
177
105
|
interface ISlugLookupController<TDoc = unknown> {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Get a resource by its slug
|
|
108
|
+
* Called by: GET /slug/:slug
|
|
109
|
+
*/
|
|
110
|
+
getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
183
111
|
}
|
|
184
112
|
/**
|
|
185
113
|
* Tree Preset Interface
|
|
@@ -209,16 +137,16 @@ interface ISlugLookupController<TDoc = unknown> {
|
|
|
209
137
|
* ```
|
|
210
138
|
*/
|
|
211
139
|
interface ITreeController<TDoc = unknown> {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Get the full hierarchical tree
|
|
142
|
+
* Called by: GET /tree
|
|
143
|
+
*/
|
|
144
|
+
getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
145
|
+
/**
|
|
146
|
+
* Get direct children of a parent node
|
|
147
|
+
* Called by: GET /:parent/children
|
|
148
|
+
*/
|
|
149
|
+
getChildren(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
222
150
|
}
|
|
223
151
|
/**
|
|
224
152
|
* Owned By User Preset
|
|
@@ -301,36 +229,40 @@ type IAuditedPreset = never;
|
|
|
301
229
|
* ```
|
|
302
230
|
*/
|
|
303
231
|
type IPresetController<TDoc = unknown, TPresets extends 'softDelete' | 'slugLookup' | 'tree' | never = never> = TPresets extends 'softDelete' ? ISoftDeleteController<TDoc> : TPresets extends 'slugLookup' ? ISlugLookupController<TDoc> : TPresets extends 'tree' ? ITreeController<TDoc> : unknown;
|
|
304
|
-
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/presets/index.d.ts
|
|
305
234
|
/**
|
|
306
235
|
* Convenience alias for multiTenantPreset with public list/get routes
|
|
307
236
|
* Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
|
|
308
237
|
*/
|
|
309
238
|
declare const flexibleMultiTenantPreset: (options?: Omit<MultiTenantOptions, "allowPublic">) => PresetResult;
|
|
310
|
-
|
|
311
239
|
type PresetFactory = (options?: AnyRecord) => PresetResult;
|
|
312
240
|
/**
|
|
313
241
|
* Get preset by name with options
|
|
314
242
|
*/
|
|
315
243
|
declare function getPreset(nameOrConfig: string | {
|
|
316
|
-
|
|
317
|
-
|
|
244
|
+
name: string;
|
|
245
|
+
[key: string]: unknown;
|
|
318
246
|
}): PresetResult;
|
|
319
247
|
/**
|
|
320
248
|
* Register a custom preset
|
|
321
249
|
*/
|
|
322
|
-
declare function registerPreset(name: string, factory: PresetFactory
|
|
250
|
+
declare function registerPreset(name: string, factory: PresetFactory, options?: {
|
|
251
|
+
override?: boolean;
|
|
252
|
+
}): void;
|
|
323
253
|
/**
|
|
324
254
|
* Get all available preset names
|
|
325
255
|
*/
|
|
326
256
|
declare function getAvailablePresets(): string[];
|
|
327
257
|
type PresetInput = string | PresetResult | {
|
|
328
|
-
|
|
329
|
-
|
|
258
|
+
name: string;
|
|
259
|
+
[key: string]: unknown;
|
|
330
260
|
};
|
|
331
261
|
/**
|
|
332
|
-
* Apply presets to resource config
|
|
262
|
+
* Apply presets to resource config.
|
|
263
|
+
* Validates preset combinations for conflicts before merging.
|
|
333
264
|
*/
|
|
334
265
|
declare function applyPresets<TDoc = AnyRecord>(config: ResourceConfig<TDoc>, presets?: PresetInput[]): ResourceConfig<TDoc>;
|
|
335
|
-
|
|
336
|
-
export { type AuditedPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type
|
|
266
|
+
//#endregion
|
|
267
|
+
export { type AuditedPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TreeOptions, applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
|
|
268
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/presets/softDelete.ts","../../src/presets/slugLookup.ts","../../src/presets/ownedByUser.ts","../../src/presets/tree.ts","../../src/presets/audited.ts","../../src/presets/types.ts","../../src/presets/index.ts"],"mappings":";;;;;;;iBAWgB,gBAAA,CAAA,GAAoB,YAAA;;;UCFnB,iBAAA;EACf,SAAA;AAAA;AAAA,iBAGc,gBAAA,CAAiB,OAAA,GAAS,iBAAA,GAAyB,YAAA;;;UCqBlD,kBAAA;EACf,UAAA;AAAA;AAAA,iBA4Bc,iBAAA,CAAkB,OAAA,GAAS,kBAAA,GAA0B,YAAA;;;UCtDpD,WAAA;EACf,WAAA;AAAA;AAAA,iBAGc,UAAA,CAAW,OAAA,GAAS,WAAA,GAAmB,YAAA;;;UCItC,oBAAA;EHRA;EGUf,cAAA;;EAEA,cAAA;AAAA;AHRF;;;AAAA,iBGcgB,aAAA,CAAc,OAAA,GAAS,oBAAA,GAA4B,YAAA;;;;;;AHdnE;;;;;;;;;;;;ACqBA;;;;;AA6BA;;;;;;;UGjBiB,qBAAA;EHiBgE;;;;EGZ/E,UAAA,CAAW,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,eAAA,CAAgB,IAAA;EF1CpD;;;;EEgD1B,OAAA,CAAQ,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;AAAA;;;;;;;;;;;ADxC7D;;;;;AAUA;;;;;;;;;;UC0DiB,qBAAA;;AAvCjB;;;EA4CE,SAAA,CAAU,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AAL/D;;UAmCiB,eAAA;EA9BA;;;;EAmCf,OAAA,CAAQ,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;EAnCnB;;;;EAyCxC,WAAA,CAAY,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,mBAAA,CAAoB,IAAA;AAAA;;;;;AAXjE;;;;;;;;;;;;;;;;KAkCY,kBAAA;;;;;;;;;;;;AAAZ;;;;;AAuBA;;;;;KAAY,kBAAA;;;;;AAwCZ;;;;;;;;;;;;;;;;;KAjBY,cAAA;;;;;;;;;;;;;ACtJZ;;;KDuKY,iBAAA,0FAGR,QAAA,wBACA,qBAAA,CAAsB,IAAA,IACtB,QAAA,wBACE,qBAAA,CAAsB,IAAA,IACtB,QAAA,kBACE,eAAA,CAAgB,IAAA;;;;AH/LxB;;;cIgBa,yBAAA,GACX,OAAA,GAAS,IAAA,CACyD,kBAAA,qBADc,YAAA;AAAA,KAgC7E,aAAA,IAAiB,OAAA,GAAU,SAAA,KAAc,YAAA;;;;iBAc9B,SAAA,CAAU,YAAA;EAAyB,IAAA;EAAA,CAAe,GAAA;AAAA,IAA0B,YAAA;AHxF5F;;;AAAA,iBGsHgB,cAAA,CACd,IAAA,UACA,OAAA,EAAS,aAAA,EACT,OAAA;EAAY,QAAA;AAAA;;;;iBAaE,mBAAA,CAAA;AAAA,KAQX,WAAA,YAAuB,YAAA;EAAiB,IAAA;EAAA,CAAe,GAAA;AAAA;;;;AFtI5D;iBEqLgB,YAAA,QAAoB,SAAA,CAAA,CAClC,MAAA,EAAQ,cAAA,CAAe,IAAA,GACvB,OAAA,GAAS,WAAA,KACR,cAAA,CAAe,IAAA"}
|