@classytic/arc 2.3.0 → 2.4.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 +187 -18
- package/bin/arc.js +11 -3
- package/dist/BaseController-CkM5dUh_.mjs +1031 -0
- package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
- package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
- package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
- package/dist/adapters/index.d.mts +3 -5
- package/dist/adapters/index.mjs +2 -3
- package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
- package/dist/audit/index.d.mts +4 -7
- package/dist/audit/index.mjs +2 -29
- package/dist/audit/mongodb.d.mts +1 -4
- package/dist/audit/mongodb.mjs +2 -3
- package/dist/auth/index.d.mts +7 -9
- package/dist/auth/index.mjs +65 -63
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +1 -2
- package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
- package/dist/cache/index.d.mts +23 -23
- package/dist/cache/index.mjs +4 -6
- package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
- package/dist/chunk-BpYLSNr0.mjs +14 -0
- package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
- package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
- package/dist/cli/commands/describe.mjs +24 -7
- package/dist/cli/commands/docs.mjs +6 -7
- package/dist/cli/commands/doctor.d.mts +10 -0
- package/dist/cli/commands/doctor.mjs +156 -0
- package/dist/cli/commands/generate.mjs +66 -17
- package/dist/cli/commands/init.mjs +315 -45
- package/dist/cli/commands/introspect.mjs +2 -4
- package/dist/cli/index.d.mts +1 -10
- package/dist/cli/index.mjs +4 -153
- package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
- package/dist/core/index.d.mts +3 -5
- package/dist/core/index.mjs +5 -4
- package/dist/core-C1XCMtqM.mjs +185 -0
- package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
- package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
- package/dist/discovery/index.mjs +37 -5
- package/dist/docs/index.d.mts +6 -9
- package/dist/docs/index.mjs +3 -21
- package/dist/dynamic/index.d.mts +93 -0
- package/dist/dynamic/index.mjs +122 -0
- package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
- package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
- package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
- package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
- package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
- package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
- package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
- package/dist/events/index.d.mts +72 -7
- package/dist/events/index.mjs +216 -4
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +19 -7
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +3 -4
- package/dist/factory/index.d.mts +23 -9
- package/dist/factory/index.mjs +48 -3
- package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
- package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
- package/dist/hooks/index.d.mts +1 -3
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +5 -5
- package/dist/idempotency/index.mjs +3 -7
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +4 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +2 -5
- package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
- package/dist/index-Diqcm14c.d.mts +369 -0
- package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
- package/dist/index.d.mts +100 -105
- package/dist/index.mjs +85 -58
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +8 -4
- package/dist/integrations/index.d.mts +4 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs.d.mts +2 -2
- package/dist/integrations/jobs.mjs +63 -14
- package/dist/integrations/mcp/index.d.mts +219 -0
- package/dist/integrations/mcp/index.mjs +572 -0
- package/dist/integrations/mcp/testing.d.mts +53 -0
- package/dist/integrations/mcp/testing.mjs +104 -0
- package/dist/integrations/streamline.mjs +39 -19
- package/dist/integrations/webhooks.d.mts +56 -0
- package/dist/integrations/webhooks.mjs +139 -0
- package/dist/integrations/websocket-redis.d.mts +46 -0
- package/dist/integrations/websocket-redis.mjs +50 -0
- package/dist/integrations/websocket.d.mts +68 -2
- package/dist/integrations/websocket.mjs +96 -13
- package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
- package/dist/interface-DGmPxakH.d.mts +2213 -0
- package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
- package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
- package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
- package/dist/metrics-Csh4nsvv.mjs +224 -0
- package/dist/migrations/index.d.mts +113 -44
- package/dist/migrations/index.mjs +84 -102
- package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
- package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
- package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
- package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
- package/dist/org/index.d.mts +12 -14
- package/dist/org/index.mjs +92 -119
- package/dist/org/types.d.mts +2 -2
- package/dist/org/types.mjs +1 -1
- package/dist/permissions/index.d.mts +4 -278
- package/dist/permissions/index.mjs +4 -579
- package/dist/permissions-CA5zg0yK.mjs +751 -0
- package/dist/plugins/index.d.mts +104 -107
- package/dist/plugins/index.mjs +203 -313
- package/dist/plugins/response-cache.mjs +4 -69
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +24 -11
- package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
- package/dist/policies/index.d.mts +2 -2
- package/dist/policies/index.mjs +80 -83
- package/dist/presets/index.d.mts +26 -19
- package/dist/presets/index.mjs +2 -142
- package/dist/presets/multiTenant.d.mts +1 -4
- package/dist/presets/multiTenant.mjs +4 -6
- package/dist/presets-C9QXJV1u.mjs +422 -0
- package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
- package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
- package/dist/queryParser-CgCtsjti.mjs +352 -0
- package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
- package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
- package/dist/registry/index.d.mts +1 -4
- package/dist/registry/index.mjs +3 -4
- package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
- package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
- package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
- package/dist/rpc/index.d.mts +90 -0
- package/dist/rpc/index.mjs +248 -0
- package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
- package/dist/schemas/index.d.mts +30 -30
- package/dist/schemas/index.mjs +2 -4
- package/dist/scope/index.d.mts +13 -2
- package/dist/scope/index.mjs +18 -5
- package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
- package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
- package/dist/testing/index.d.mts +551 -567
- package/dist/testing/index.mjs +1744 -1799
- package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
- package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
- package/dist/types/index.d.mts +4 -946
- package/dist/types/index.mjs +2 -4
- package/dist/types-BJmgxNbF.d.mts +275 -0
- package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
- package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
- package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
- package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
- package/dist/utils/index.d.mts +254 -351
- package/dist/utils/index.mjs +7 -6
- package/dist/utils-Dc0WhlIl.mjs +594 -0
- package/dist/versioning-BzfeHmhj.mjs +37 -0
- package/package.json +44 -10
- package/skills/arc/SKILL.md +518 -0
- package/skills/arc/references/auth.md +250 -0
- package/skills/arc/references/events.md +272 -0
- package/skills/arc/references/integrations.md +385 -0
- package/skills/arc/references/mcp.md +431 -0
- package/skills/arc/references/production.md +610 -0
- package/skills/arc/references/testing.md +183 -0
- package/dist/audited-CGdLiSlE.mjs +0 -140
- package/dist/chunk-C7Uep-_p.mjs +0 -20
- package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
- package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
- package/dist/interface-BtdYtQUA.d.mts +0 -1114
- package/dist/presets-BTeYbw7h.d.mts +0 -57
- package/dist/presets-CeFtfDR8.mjs +0 -119
- /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
- /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
- /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
|
@@ -1,579 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
|
|
5
|
-
import { a as presets_exports, c as readOnly, i as ownerWithAdminBypass, n as authenticated, o as publicRead, r as fullPublic, s as publicReadAdminWrite, t as adminOnly } from "../presets-CeFtfDR8.mjs";
|
|
6
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
-
|
|
8
|
-
//#region src/permissions/index.ts
|
|
9
|
-
/**
|
|
10
|
-
* Allow public access (no authentication required)
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* permissions: {
|
|
15
|
-
* list: allowPublic(),
|
|
16
|
-
* get: allowPublic(),
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
function allowPublic() {
|
|
21
|
-
const check = () => true;
|
|
22
|
-
check._isPublic = true;
|
|
23
|
-
return check;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Require authentication (any authenticated user)
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* permissions: {
|
|
31
|
-
* create: requireAuth(),
|
|
32
|
-
* update: requireAuth(),
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
function requireAuth() {
|
|
37
|
-
const check = (ctx) => {
|
|
38
|
-
if (!ctx.user) return {
|
|
39
|
-
granted: false,
|
|
40
|
-
reason: "Authentication required"
|
|
41
|
-
};
|
|
42
|
-
return true;
|
|
43
|
-
};
|
|
44
|
-
return check;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Require specific roles
|
|
48
|
-
*
|
|
49
|
-
* @param roles - Required roles (user needs at least one)
|
|
50
|
-
* @param options - Optional bypass roles
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* permissions: {
|
|
55
|
-
* create: requireRoles(['admin', 'editor']),
|
|
56
|
-
* delete: requireRoles(['admin']),
|
|
57
|
-
* }
|
|
58
|
-
*
|
|
59
|
-
* // With bypass roles
|
|
60
|
-
* permissions: {
|
|
61
|
-
* update: requireRoles(['owner'], { bypassRoles: ['admin', 'superadmin'] }),
|
|
62
|
-
* }
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
function requireRoles(roles, options) {
|
|
66
|
-
const check = (ctx) => {
|
|
67
|
-
if (!ctx.user) return {
|
|
68
|
-
granted: false,
|
|
69
|
-
reason: "Authentication required"
|
|
70
|
-
};
|
|
71
|
-
const userRoles = getUserRoles(ctx.user);
|
|
72
|
-
if (options?.bypassRoles?.some((r) => userRoles.includes(r))) return true;
|
|
73
|
-
if (roles.some((r) => userRoles.includes(r))) return true;
|
|
74
|
-
return {
|
|
75
|
-
granted: false,
|
|
76
|
-
reason: `Required roles: ${roles.join(", ")}`
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
check._roles = roles;
|
|
80
|
-
return check;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Require resource ownership
|
|
84
|
-
*
|
|
85
|
-
* Returns filters to scope queries to user's owned resources.
|
|
86
|
-
*
|
|
87
|
-
* @param ownerField - Field containing owner ID (default: 'userId')
|
|
88
|
-
* @param options - Optional bypass roles
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```typescript
|
|
92
|
-
* permissions: {
|
|
93
|
-
* update: requireOwnership('userId'),
|
|
94
|
-
* delete: requireOwnership('createdBy', { bypassRoles: ['admin'] }),
|
|
95
|
-
* }
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
function requireOwnership(ownerField = "userId", options) {
|
|
99
|
-
return (ctx) => {
|
|
100
|
-
if (!ctx.user) return {
|
|
101
|
-
granted: false,
|
|
102
|
-
reason: "Authentication required"
|
|
103
|
-
};
|
|
104
|
-
const userRoles = getUserRoles(ctx.user);
|
|
105
|
-
if (options?.bypassRoles?.some((r) => userRoles.includes(r))) return true;
|
|
106
|
-
const userId = ctx.user.id ?? ctx.user._id;
|
|
107
|
-
if (!userId) return {
|
|
108
|
-
granted: false,
|
|
109
|
-
reason: "User identity missing (no id or _id)"
|
|
110
|
-
};
|
|
111
|
-
return {
|
|
112
|
-
granted: true,
|
|
113
|
-
filters: { [ownerField]: userId }
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Combine multiple checks - ALL must pass (AND logic)
|
|
119
|
-
*
|
|
120
|
-
* @example
|
|
121
|
-
* ```typescript
|
|
122
|
-
* permissions: {
|
|
123
|
-
* update: allOf(
|
|
124
|
-
* requireAuth(),
|
|
125
|
-
* requireRoles(['editor']),
|
|
126
|
-
* requireOwnership('createdBy')
|
|
127
|
-
* ),
|
|
128
|
-
* }
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
function allOf(...checks) {
|
|
132
|
-
return async (ctx) => {
|
|
133
|
-
let mergedFilters = {};
|
|
134
|
-
for (const check of checks) {
|
|
135
|
-
const result = await check(ctx);
|
|
136
|
-
const normalized = typeof result === "boolean" ? { granted: result } : result;
|
|
137
|
-
if (!normalized.granted) return normalized;
|
|
138
|
-
if (normalized.filters) mergedFilters = {
|
|
139
|
-
...mergedFilters,
|
|
140
|
-
...normalized.filters
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
granted: true,
|
|
145
|
-
filters: Object.keys(mergedFilters).length > 0 ? mergedFilters : void 0
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Combine multiple checks - ANY must pass (OR logic)
|
|
151
|
-
*
|
|
152
|
-
* @example
|
|
153
|
-
* ```typescript
|
|
154
|
-
* permissions: {
|
|
155
|
-
* update: anyOf(
|
|
156
|
-
* requireRoles(['admin']),
|
|
157
|
-
* requireOwnership('createdBy')
|
|
158
|
-
* ),
|
|
159
|
-
* }
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
function anyOf(...checks) {
|
|
163
|
-
return async (ctx) => {
|
|
164
|
-
const reasons = [];
|
|
165
|
-
for (const check of checks) {
|
|
166
|
-
const result = await check(ctx);
|
|
167
|
-
const normalized = typeof result === "boolean" ? { granted: result } : result;
|
|
168
|
-
if (normalized.granted) return normalized;
|
|
169
|
-
if (normalized.reason) reasons.push(normalized.reason);
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
granted: false,
|
|
173
|
-
reason: reasons.join("; ")
|
|
174
|
-
};
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Deny all access
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```typescript
|
|
182
|
-
* permissions: {
|
|
183
|
-
* delete: denyAll('Deletion not allowed'),
|
|
184
|
-
* }
|
|
185
|
-
* ```
|
|
186
|
-
*/
|
|
187
|
-
function denyAll(reason = "Access denied") {
|
|
188
|
-
return () => ({
|
|
189
|
-
granted: false,
|
|
190
|
-
reason
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Dynamic permission based on context
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* ```typescript
|
|
198
|
-
* permissions: {
|
|
199
|
-
* update: when((ctx) => ctx.data?.status === 'draft'),
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
function when(condition) {
|
|
204
|
-
return async (ctx) => {
|
|
205
|
-
const result = await condition(ctx);
|
|
206
|
-
return {
|
|
207
|
-
granted: result,
|
|
208
|
-
reason: result ? void 0 : "Condition not met"
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
/** Read request.scope safely */
|
|
213
|
-
function getScope(request) {
|
|
214
|
-
return request.scope ?? PUBLIC_SCOPE;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Require membership in the active organization.
|
|
218
|
-
* User must be authenticated AND have an active org (member or elevated scope).
|
|
219
|
-
*
|
|
220
|
-
* Reads `request.scope` set by auth adapters.
|
|
221
|
-
*
|
|
222
|
-
* @example
|
|
223
|
-
* ```typescript
|
|
224
|
-
* permissions: {
|
|
225
|
-
* list: requireOrgMembership(),
|
|
226
|
-
* get: requireOrgMembership(),
|
|
227
|
-
* }
|
|
228
|
-
* ```
|
|
229
|
-
*/
|
|
230
|
-
function requireOrgMembership() {
|
|
231
|
-
const check = (ctx) => {
|
|
232
|
-
if (!ctx.user) return {
|
|
233
|
-
granted: false,
|
|
234
|
-
reason: "Authentication required"
|
|
235
|
-
};
|
|
236
|
-
const scope = getScope(ctx.request);
|
|
237
|
-
if (isElevated(scope)) return true;
|
|
238
|
-
if (isMember(scope)) return true;
|
|
239
|
-
return {
|
|
240
|
-
granted: false,
|
|
241
|
-
reason: "Organization membership required"
|
|
242
|
-
};
|
|
243
|
-
};
|
|
244
|
-
check._orgPermission = "membership";
|
|
245
|
-
return check;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Require specific org-level roles.
|
|
249
|
-
* Reads `request.scope.orgRoles` (set by auth adapters).
|
|
250
|
-
* Elevated scope always passes (platform admin bypass).
|
|
251
|
-
*
|
|
252
|
-
* @param roles - Required org roles (user needs at least one)
|
|
253
|
-
*
|
|
254
|
-
* @example
|
|
255
|
-
* ```typescript
|
|
256
|
-
* permissions: {
|
|
257
|
-
* create: requireOrgRole('admin', 'owner'),
|
|
258
|
-
* delete: requireOrgRole('owner'),
|
|
259
|
-
* }
|
|
260
|
-
* ```
|
|
261
|
-
*/
|
|
262
|
-
function requireOrgRole(...args) {
|
|
263
|
-
const roles = Array.isArray(args[0]) ? args[0] : args;
|
|
264
|
-
const check = (ctx) => {
|
|
265
|
-
if (!ctx.user) return {
|
|
266
|
-
granted: false,
|
|
267
|
-
reason: "Authentication required"
|
|
268
|
-
};
|
|
269
|
-
const scope = getScope(ctx.request);
|
|
270
|
-
if (isElevated(scope)) return true;
|
|
271
|
-
if (!isMember(scope)) return {
|
|
272
|
-
granted: false,
|
|
273
|
-
reason: "Organization membership required"
|
|
274
|
-
};
|
|
275
|
-
if (roles.some((r) => scope.orgRoles.includes(r))) return true;
|
|
276
|
-
return {
|
|
277
|
-
granted: false,
|
|
278
|
-
reason: `Required org roles: ${roles.join(", ")}`
|
|
279
|
-
};
|
|
280
|
-
};
|
|
281
|
-
check._orgRoles = roles;
|
|
282
|
-
return check;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Create a scoped permission system for resource-action patterns.
|
|
286
|
-
* Maps org roles to fine-grained permissions without external API calls.
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* ```typescript
|
|
290
|
-
* const perms = createOrgPermissions({
|
|
291
|
-
* statements: {
|
|
292
|
-
* product: ['create', 'update', 'delete'],
|
|
293
|
-
* order: ['create', 'approve'],
|
|
294
|
-
* },
|
|
295
|
-
* roles: {
|
|
296
|
-
* owner: { product: ['create', 'update', 'delete'], order: ['create', 'approve'] },
|
|
297
|
-
* admin: { product: ['create', 'update'], order: ['create'] },
|
|
298
|
-
* member: { product: [], order: [] },
|
|
299
|
-
* },
|
|
300
|
-
* });
|
|
301
|
-
*
|
|
302
|
-
* defineResource({
|
|
303
|
-
* permissions: {
|
|
304
|
-
* create: perms.can({ product: ['create'] }),
|
|
305
|
-
* delete: perms.can({ product: ['delete'] }),
|
|
306
|
-
* }
|
|
307
|
-
* });
|
|
308
|
-
* ```
|
|
309
|
-
*/
|
|
310
|
-
function createOrgPermissions(config) {
|
|
311
|
-
const { roles: roleMap } = config;
|
|
312
|
-
function hasPermissions(orgRoles, required) {
|
|
313
|
-
for (const [resource, actions] of Object.entries(required)) for (const action of actions) if (!orgRoles.some((role) => {
|
|
314
|
-
return (roleMap[role]?.[resource])?.includes(action);
|
|
315
|
-
})) return false;
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
318
|
-
return {
|
|
319
|
-
can(permissions) {
|
|
320
|
-
return (ctx) => {
|
|
321
|
-
if (!ctx.user) return {
|
|
322
|
-
granted: false,
|
|
323
|
-
reason: "Authentication required"
|
|
324
|
-
};
|
|
325
|
-
const scope = getScope(ctx.request);
|
|
326
|
-
if (isElevated(scope)) return true;
|
|
327
|
-
if (!isMember(scope)) return {
|
|
328
|
-
granted: false,
|
|
329
|
-
reason: "Organization membership required"
|
|
330
|
-
};
|
|
331
|
-
if (hasPermissions(scope.orgRoles, permissions)) return true;
|
|
332
|
-
return {
|
|
333
|
-
granted: false,
|
|
334
|
-
reason: `Missing permissions: ${Object.entries(permissions).map(([r, a]) => `${r}:[${a.join(",")}]`).join(", ")}`
|
|
335
|
-
};
|
|
336
|
-
};
|
|
337
|
-
},
|
|
338
|
-
requireRole(...roles) {
|
|
339
|
-
return requireOrgRole(roles);
|
|
340
|
-
},
|
|
341
|
-
requireMembership() {
|
|
342
|
-
return requireOrgMembership();
|
|
343
|
-
},
|
|
344
|
-
requireTeamMembership() {
|
|
345
|
-
return requireTeamMembership();
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Create a dynamic role-based permission matrix.
|
|
351
|
-
*
|
|
352
|
-
* Use this when role/action mappings are managed outside code
|
|
353
|
-
* (e.g., admin UI matrix, DB-stored ACLs, remote policy service).
|
|
354
|
-
*
|
|
355
|
-
* Supports:
|
|
356
|
-
* - org role union (any assigned org role can grant)
|
|
357
|
-
* - global bypass roles
|
|
358
|
-
* - wildcard resource/action (`*`)
|
|
359
|
-
* - optional in-memory cache
|
|
360
|
-
*/
|
|
361
|
-
function createDynamicPermissionMatrix(config) {
|
|
362
|
-
const logger = config.logger ?? console;
|
|
363
|
-
const legacyTtlMs = config.cache?.ttlMs ?? 0;
|
|
364
|
-
const hasExternalStore = !!config.cacheStore;
|
|
365
|
-
const cacheTtlMs = legacyTtlMs > 0 ? legacyTtlMs : hasExternalStore ? 3e5 : 0;
|
|
366
|
-
const internalStore = !config.cacheStore && cacheTtlMs > 0 ? new MemoryCacheStore({
|
|
367
|
-
defaultTtlMs: cacheTtlMs,
|
|
368
|
-
maxEntries: config.cache?.maxEntries ?? 1e3
|
|
369
|
-
}) : void 0;
|
|
370
|
-
const cacheStore = config.cacheStore ?? internalStore;
|
|
371
|
-
const trackedKeys = /* @__PURE__ */ new Set();
|
|
372
|
-
const nodeId = randomUUID().slice(0, 8);
|
|
373
|
-
const DEFAULT_EVENT_TYPE = "arc.permissions.invalidated";
|
|
374
|
-
let eventBridge = null;
|
|
375
|
-
/** Clear local cache for an org without publishing events (avoids infinite loops). */
|
|
376
|
-
async function localInvalidateByOrg(orgId) {
|
|
377
|
-
if (!cacheStore) return;
|
|
378
|
-
const prefix = `${orgId}::`;
|
|
379
|
-
const toDelete = [];
|
|
380
|
-
for (const key of trackedKeys) if (key.startsWith(prefix)) toDelete.push(key);
|
|
381
|
-
for (const key of toDelete) try {
|
|
382
|
-
await cacheStore.delete(key);
|
|
383
|
-
trackedKeys.delete(key);
|
|
384
|
-
} catch (error) {
|
|
385
|
-
logger.warn(`[DynamicPermissionMatrix] invalidateByOrg delete failed for '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
function isActionAllowed(actions, action) {
|
|
389
|
-
if (!actions || actions.length === 0) return false;
|
|
390
|
-
return actions.includes("*") || actions.includes(action);
|
|
391
|
-
}
|
|
392
|
-
function roleAllows(matrix, role, resource, action) {
|
|
393
|
-
const rolePermissions = matrix[role];
|
|
394
|
-
if (!rolePermissions) return false;
|
|
395
|
-
const resourceActions = rolePermissions[resource];
|
|
396
|
-
const wildcardResourceActions = rolePermissions["*"];
|
|
397
|
-
return isActionAllowed(resourceActions, action) || isActionAllowed(wildcardResourceActions, action);
|
|
398
|
-
}
|
|
399
|
-
function buildDefaultCacheKey(ctx, orgId, orgRoles) {
|
|
400
|
-
const userId = String(ctx.user?.id ?? ctx.user?._id ?? "anon");
|
|
401
|
-
const roles = (orgRoles ?? []).slice().sort().join(",");
|
|
402
|
-
return `${orgId ?? "no-org"}::${roles}::${userId}`;
|
|
403
|
-
}
|
|
404
|
-
async function resolveMatrix(ctx, orgId, orgRoles) {
|
|
405
|
-
if (!cacheStore) return config.resolveRolePermissions(ctx);
|
|
406
|
-
const cacheKey = config.cache?.key?.(ctx) ?? buildDefaultCacheKey(ctx, orgId, orgRoles);
|
|
407
|
-
if (!cacheKey) return config.resolveRolePermissions(ctx);
|
|
408
|
-
try {
|
|
409
|
-
const hit = await cacheStore.get(cacheKey);
|
|
410
|
-
if (hit) return hit;
|
|
411
|
-
} catch (error) {
|
|
412
|
-
logger.warn(`[DynamicPermissionMatrix] Cache get failed for '${cacheKey}': ${error instanceof Error ? error.message : String(error)}`);
|
|
413
|
-
}
|
|
414
|
-
const value = await config.resolveRolePermissions(ctx);
|
|
415
|
-
try {
|
|
416
|
-
await cacheStore.set(cacheKey, value, { ttlMs: cacheTtlMs });
|
|
417
|
-
trackedKeys.add(cacheKey);
|
|
418
|
-
const maxTracked = config.cache?.maxEntries ?? 1e4;
|
|
419
|
-
if (trackedKeys.size > maxTracked) {
|
|
420
|
-
const overflow = trackedKeys.size - maxTracked;
|
|
421
|
-
const iter = trackedKeys.values();
|
|
422
|
-
for (let i = 0; i < overflow; i++) {
|
|
423
|
-
const oldest = iter.next().value;
|
|
424
|
-
if (oldest) trackedKeys.delete(oldest);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
} catch (error) {
|
|
428
|
-
logger.warn(`[DynamicPermissionMatrix] Cache set failed for '${cacheKey}': ${error instanceof Error ? error.message : String(error)}`);
|
|
429
|
-
}
|
|
430
|
-
return value;
|
|
431
|
-
}
|
|
432
|
-
function can(required) {
|
|
433
|
-
return async (ctx) => {
|
|
434
|
-
if (!ctx.user) return {
|
|
435
|
-
granted: false,
|
|
436
|
-
reason: "Authentication required"
|
|
437
|
-
};
|
|
438
|
-
const scope = getScope(ctx.request);
|
|
439
|
-
if (isElevated(scope)) return true;
|
|
440
|
-
if (!isMember(scope)) return {
|
|
441
|
-
granted: false,
|
|
442
|
-
reason: "Organization membership required"
|
|
443
|
-
};
|
|
444
|
-
const orgRoles = scope.orgRoles;
|
|
445
|
-
if (orgRoles.length === 0) return {
|
|
446
|
-
granted: false,
|
|
447
|
-
reason: "Not a member of this organization"
|
|
448
|
-
};
|
|
449
|
-
let matrix;
|
|
450
|
-
try {
|
|
451
|
-
matrix = await resolveMatrix(ctx, scope.organizationId, orgRoles);
|
|
452
|
-
} catch (error) {
|
|
453
|
-
return {
|
|
454
|
-
granted: false,
|
|
455
|
-
reason: `Permission matrix resolution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
for (const [resource, actions] of Object.entries(required)) for (const action of actions) if (!orgRoles.some((role) => roleAllows(matrix, role, resource, action))) return {
|
|
459
|
-
granted: false,
|
|
460
|
-
reason: `Missing permission: ${resource}:${action}`
|
|
461
|
-
};
|
|
462
|
-
return true;
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
return {
|
|
466
|
-
can,
|
|
467
|
-
canAction(resource, action) {
|
|
468
|
-
return can({ [resource]: [action] });
|
|
469
|
-
},
|
|
470
|
-
requireRole(...roles) {
|
|
471
|
-
return requireOrgRole(roles);
|
|
472
|
-
},
|
|
473
|
-
requireMembership() {
|
|
474
|
-
return requireOrgMembership();
|
|
475
|
-
},
|
|
476
|
-
requireTeamMembership() {
|
|
477
|
-
return requireTeamMembership();
|
|
478
|
-
},
|
|
479
|
-
async invalidateByOrg(orgId) {
|
|
480
|
-
await localInvalidateByOrg(orgId);
|
|
481
|
-
if (eventBridge) try {
|
|
482
|
-
await eventBridge.publish(eventBridge.eventType, {
|
|
483
|
-
orgId,
|
|
484
|
-
nodeId
|
|
485
|
-
});
|
|
486
|
-
} catch (error) {
|
|
487
|
-
logger.warn(`[DynamicPermissionMatrix] Failed to publish invalidation event for org '${orgId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
async clearCache() {
|
|
491
|
-
if (!cacheStore) return;
|
|
492
|
-
if (cacheStore.clear) try {
|
|
493
|
-
await cacheStore.clear();
|
|
494
|
-
trackedKeys.clear();
|
|
495
|
-
return;
|
|
496
|
-
} catch (error) {
|
|
497
|
-
logger.warn(`[DynamicPermissionMatrix] cacheStore.clear failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
498
|
-
}
|
|
499
|
-
for (const key of trackedKeys) try {
|
|
500
|
-
await cacheStore.delete(key);
|
|
501
|
-
} catch (error) {
|
|
502
|
-
logger.warn(`[DynamicPermissionMatrix] Cache delete failed for '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
503
|
-
}
|
|
504
|
-
trackedKeys.clear();
|
|
505
|
-
},
|
|
506
|
-
async connectEvents(events, options) {
|
|
507
|
-
if (eventBridge) await this.disconnectEvents();
|
|
508
|
-
const eventType = options?.eventType ?? DEFAULT_EVENT_TYPE;
|
|
509
|
-
const unsubscribeFn = await events.subscribe(eventType, async (event) => {
|
|
510
|
-
const payload = event.payload;
|
|
511
|
-
if (!payload?.orgId) return;
|
|
512
|
-
if (payload.nodeId === nodeId) return;
|
|
513
|
-
await localInvalidateByOrg(payload.orgId);
|
|
514
|
-
if (options?.onRemoteInvalidation) try {
|
|
515
|
-
await options.onRemoteInvalidation(payload.orgId);
|
|
516
|
-
} catch (error) {
|
|
517
|
-
logger.warn(`[DynamicPermissionMatrix] onRemoteInvalidation callback failed for org '${payload.orgId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
eventBridge = {
|
|
521
|
-
publish: events.publish,
|
|
522
|
-
unsubscribe: typeof unsubscribeFn === "function" ? unsubscribeFn : null,
|
|
523
|
-
eventType,
|
|
524
|
-
onRemoteInvalidation: options?.onRemoteInvalidation
|
|
525
|
-
};
|
|
526
|
-
},
|
|
527
|
-
async disconnectEvents() {
|
|
528
|
-
if (!eventBridge) return;
|
|
529
|
-
try {
|
|
530
|
-
eventBridge.unsubscribe?.();
|
|
531
|
-
} catch (error) {
|
|
532
|
-
logger.warn(`[DynamicPermissionMatrix] disconnectEvents unsubscribe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
533
|
-
}
|
|
534
|
-
eventBridge = null;
|
|
535
|
-
},
|
|
536
|
-
get eventsConnected() {
|
|
537
|
-
return eventBridge !== null;
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Require membership in the active team.
|
|
543
|
-
* User must be authenticated, a member of the active org, AND have an active team.
|
|
544
|
-
*
|
|
545
|
-
* Better Auth teams are flat member groups (no team-level roles).
|
|
546
|
-
* Reads `request.scope.teamId` set by the Better Auth adapter.
|
|
547
|
-
*
|
|
548
|
-
* @example
|
|
549
|
-
* ```typescript
|
|
550
|
-
* permissions: {
|
|
551
|
-
* list: requireTeamMembership(),
|
|
552
|
-
* create: requireTeamMembership(),
|
|
553
|
-
* }
|
|
554
|
-
* ```
|
|
555
|
-
*/
|
|
556
|
-
function requireTeamMembership() {
|
|
557
|
-
const check = (ctx) => {
|
|
558
|
-
if (!ctx.user) return {
|
|
559
|
-
granted: false,
|
|
560
|
-
reason: "Authentication required"
|
|
561
|
-
};
|
|
562
|
-
const scope = getScope(ctx.request);
|
|
563
|
-
if (isElevated(scope)) return true;
|
|
564
|
-
if (!isMember(scope)) return {
|
|
565
|
-
granted: false,
|
|
566
|
-
reason: "Organization membership required"
|
|
567
|
-
};
|
|
568
|
-
if (!getTeamId(scope)) return {
|
|
569
|
-
granted: false,
|
|
570
|
-
reason: "No active team"
|
|
571
|
-
};
|
|
572
|
-
return true;
|
|
573
|
-
};
|
|
574
|
-
check._teamPermission = "membership";
|
|
575
|
-
return check;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
//#endregion
|
|
579
|
-
export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDynamicPermissionMatrix, createOrgPermissions, denyAll, fields, fullPublic, getUserRoles, normalizeRoles, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, resolveEffectiveRoles, when };
|
|
1
|
+
import { i as resolveEffectiveRoles, n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "../fields-ipsbIRPK.mjs";
|
|
2
|
+
import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
|
|
3
|
+
import { S as createRoleHierarchy, _ as ownerWithAdminBypass, a as createOrgPermissions, b as publicReadAdminWrite, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as fullPublic, h as authenticated, i as createDynamicPermissionMatrix, l as requireOrgRole, m as adminOnly, n as allowPublic, o as denyAll, p as when, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as presets_exports, x as readOnly, y as publicRead } from "../permissions-CA5zg0yK.mjs";
|
|
4
|
+
export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizeRoles, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, resolveEffectiveRoles, when };
|