@classytic/arc 2.9.1 → 2.10.3
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 +19 -90
- package/dist/{BaseController-Vu2yc56T.mjs → BaseController-CbKKIflT.mjs} +8 -44
- package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.mjs +2 -2
- package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
- package/dist/audit/index.d.mts +38 -3
- package/dist/audit/index.mjs +41 -7
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +5 -5
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/cache/index.d.mts +17 -15
- package/dist/cache/index.mjs +15 -14
- package/dist/{caching-CjybdRwx.mjs → caching-CBpK_SCM.mjs} +8 -3
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -4
- package/dist/{defineResource-C__jkwvs.mjs → core-CcR01lup.mjs} +44 -12
- package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-Bp_5c_2b.mjs} +1 -1
- package/dist/{createApp-CBJUJKGP.mjs → createApp-BuvPma24.mjs} +14 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DxQ6ACbt.mjs → elevation-C7hgL_aI.mjs} +2 -2
- package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-Bb49BvPD.mjs} +1 -1
- package/dist/{errorHandler-DixGcttC.d.mts → errorHandler-DRQ3EqfL.d.mts} +1 -1
- package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-CxWgpd6K.d.mts} +1 -1
- package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-DCUjuiQT.mjs} +1 -1
- package/dist/events/index.d.mts +8 -5
- package/dist/events/index.mjs +34 -17
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
- package/dist/{filesUpload-q8oHt--L.mjs → filesUpload-t21LS-py.mjs} +2 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +7 -4
- package/dist/idempotency/index.mjs +9 -11
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-Cibkchnx.d.mts → index-8qw4y6ff.d.mts} +2 -2
- package/dist/{index-C-xjcA6F.d.mts → index-ChIw3776.d.mts} +283 -408
- package/dist/{interface-YrWsmKqE.d.mts → index-Cl0uoKd5.d.mts} +1885 -2741
- package/dist/{index-CtGKT0lf.d.mts → index-DStwgFUK.d.mts} +81 -7
- package/dist/index.d.mts +7 -8
- package/dist/index.mjs +11 -12
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/interface-D218ikEo.d.mts +77 -0
- package/dist/{memory-BFAYkf8H.mjs → memory-B5Amv9A1.mjs} +23 -8
- package/dist/{openapi-CXuTG1M9.mjs → openapi-B5F8AddX.mjs} +2 -2
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -4
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-oNZawnkR.mjs → permissions-Dk6mshja.mjs} +315 -397
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +12 -14
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +91 -4
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-hM4WhNWY.mjs → presets-fLJVXdVn.mjs} +1 -1
- package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
- package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-DQCEfJis.mjs} +8 -8
- package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-DBqBB6AC.mjs} +1 -1
- package/dist/{redis-MXLp1oOf.d.mts → redis-DqyeggCa.d.mts} +1 -1
- package/dist/{redis-stream-Bz-4q96t.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{resourceToTools-C3cWymnW.mjs → resourceToTools-BElv3xPT.mjs} +3 -3
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-CJpt7LGI.mjs → sse-yBCgOLGu.mjs} +1 -1
- package/dist/testing/index.d.mts +6 -5
- package/dist/testing/index.mjs +8 -10
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -31
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-CoSzA-s-.d.mts → types-Btdda02s.d.mts} +1 -1
- package/dist/{types-CunEX4UX.d.mts → types-Co8k3NyS.d.mts} +9 -9
- package/dist/types-Csi3FLfq.mjs +27 -0
- package/dist/utils/index.d.mts +207 -3
- package/dist/utils/index.mjs +3 -4
- package/dist/{utils-B7FuRr9w.mjs → utils-B2fNOD_i.mjs} +285 -2
- package/dist/{versioning-Cm8qoFDg.mjs → versioning-C2U_bLY0.mjs} +3 -5
- package/package.json +15 -18
- package/skills/arc/SKILL.md +7 -11
- package/skills/arc/references/production.md +0 -41
- package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
- package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
- package/dist/core-DNncu0xF.mjs +0 -34
- package/dist/dynamic/index.d.mts +0 -93
- package/dist/dynamic/index.mjs +0 -122
- package/dist/fields-BC7zcmI9.d.mts +0 -121
- package/dist/interface-DplgQO2e.d.mts +0 -54
- package/dist/policies/index.d.mts +0 -425
- package/dist/policies/index.mjs +0 -318
- package/dist/rpc/index.d.mts +0 -90
- package/dist/rpc/index.mjs +0 -248
- /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CUw5NNWe.d.mts} +0 -0
- /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
- /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
- /package/dist/{betterAuthOpenApi--rdY15Ld.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +0 -0
- /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
- /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
- /package/dist/{errors-BI8kEKsO.d.mts → errors-CCSsMpXE.d.mts} +0 -0
- /package/dist/{errors-CqWnSqM-.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
- /package/dist/{fields-CU6FlaDV.mjs → fields-bxkeltzz.mjs} +0 -0
- /package/dist/{interface-B-pe8fhj.d.mts → interface-CSbZdv_3.d.mts} +0 -0
- /package/dist/{loadResources-Bksk8ydA.mjs → loadResources-BAzJItAJ.mjs} +0 -0
- /package/dist/{logger-CDjpjySd.mjs → logger-DLg8-Ueg.mjs} +0 -0
- /package/dist/{metrics-TuOmguhi.mjs → metrics-DuhiSEZI.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{registry-B0Wl7uVV.mjs → registry-B3lRFBWo.mjs} +0 -0
- /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
- /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
- /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
- /package/dist/{storage-BwGQXUpd.d.mts → storage-CVk_SEn2.d.mts} +0 -0
- /package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-ZCSMJJAX.mjs} +0 -0
- /package/dist/{tracing-xqXzWeaf.d.mts → tracing-65B51Dw3.d.mts} +0 -0
- /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
|
|
2
|
-
import { FastifyReply, FastifyRequest } from "fastify";
|
|
3
|
-
|
|
4
|
-
//#region src/policies/PolicyInterface.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Policy result returned by can() method
|
|
7
|
-
*/
|
|
8
|
-
interface PolicyResult {
|
|
9
|
-
/**
|
|
10
|
-
* Whether the operation is allowed
|
|
11
|
-
*/
|
|
12
|
-
allowed: boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Human-readable reason if denied
|
|
15
|
-
* Returned in 403 error responses
|
|
16
|
-
*/
|
|
17
|
-
reason?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Query filters to apply (for list operations).
|
|
20
|
-
*
|
|
21
|
-
* Values are `unknown` because filter dialects differ (MongoDB operator
|
|
22
|
-
* objects, SQL WHERE AST, structured DSLs) — adapters narrow at the edge.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* // Multi-tenant filter
|
|
27
|
-
* { organizationId: user.organizationId }
|
|
28
|
-
*
|
|
29
|
-
* // Complex filter
|
|
30
|
-
* { $or: [{ public: true }, { createdBy: user.id }] }
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
filters?: Record<string, unknown>;
|
|
34
|
-
/**
|
|
35
|
-
* Fields to include/exclude in response
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```typescript
|
|
39
|
-
* // Hide sensitive fields from non-admins
|
|
40
|
-
* { exclude: ['password', 'ssn', 'salary'] }
|
|
41
|
-
*
|
|
42
|
-
* // Only show specific fields
|
|
43
|
-
* { include: ['name', 'email', 'role'] }
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
fieldMask?: {
|
|
47
|
-
include?: string[];
|
|
48
|
-
exclude?: string[];
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Additional context for downstream middleware.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```typescript
|
|
55
|
-
* {
|
|
56
|
-
* auditLog: { action: 'read', resource: 'patient', userId: user.id },
|
|
57
|
-
* rateLimit: { tier: user.subscriptionTier },
|
|
58
|
-
* }
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
metadata?: Record<string, unknown>;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Policy context provided to `can()`.
|
|
65
|
-
*
|
|
66
|
-
* Fields are `unknown` by design: the shape depends on the adapter and the
|
|
67
|
-
* operation (e.g. `document` is the repository's row type, `body` is the
|
|
68
|
-
* wire DTO). Implementations narrow at the call site with a type guard or
|
|
69
|
-
* generic — this is the only honest contract across adapters.
|
|
70
|
-
*/
|
|
71
|
-
interface PolicyContext {
|
|
72
|
-
/** The document being accessed (for update/delete/get). Populated by fetchDocument middleware. */
|
|
73
|
-
document?: unknown;
|
|
74
|
-
/** Request body (for create/update). */
|
|
75
|
-
body?: unknown;
|
|
76
|
-
/** Request params (e.g., `:id` from route). */
|
|
77
|
-
params?: unknown;
|
|
78
|
-
/** Request query parameters. */
|
|
79
|
-
query?: unknown;
|
|
80
|
-
/** Additional app-specific context keyed by policy-defined names. */
|
|
81
|
-
[key: string]: unknown;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Policy Engine Interface
|
|
85
|
-
*
|
|
86
|
-
* Implement this interface to create your own authorization strategy.
|
|
87
|
-
*
|
|
88
|
-
* Arc provides the interface, apps provide the implementation.
|
|
89
|
-
* This follows the same pattern as:
|
|
90
|
-
* - Database drivers (interface: query(), implementation: PostgreSQL, MySQL)
|
|
91
|
-
* - Storage providers (interface: upload(), implementation: S3, Azure)
|
|
92
|
-
* - Authentication strategies (interface: verify(), implementation: JWT, OAuth)
|
|
93
|
-
*
|
|
94
|
-
* @example E-commerce RBAC + Ownership
|
|
95
|
-
* ```typescript
|
|
96
|
-
* class EcommercePolicyEngine implements PolicyEngine {
|
|
97
|
-
* constructor(private config: { roles: Record<string, string[]> }) {}
|
|
98
|
-
*
|
|
99
|
-
* can(user, operation, context) {
|
|
100
|
-
* // Check RBAC
|
|
101
|
-
* const allowedRoles = this.config.roles[operation] || [];
|
|
102
|
-
* if (!getUserRoles(user).some(r => allowedRoles.includes(r))) {
|
|
103
|
-
* return { allowed: false, reason: 'Insufficient permissions' };
|
|
104
|
-
* }
|
|
105
|
-
*
|
|
106
|
-
* // Check ownership for update/delete
|
|
107
|
-
* if (['update', 'delete'].includes(operation)) {
|
|
108
|
-
* if (context.document.userId !== user.id) {
|
|
109
|
-
* return { allowed: false, reason: 'Not the owner' };
|
|
110
|
-
* }
|
|
111
|
-
* }
|
|
112
|
-
*
|
|
113
|
-
* // Multi-tenant filter for list
|
|
114
|
-
* if (operation === 'list') {
|
|
115
|
-
* return {
|
|
116
|
-
* allowed: true,
|
|
117
|
-
* filters: { organizationId: user.organizationId },
|
|
118
|
-
* };
|
|
119
|
-
* }
|
|
120
|
-
*
|
|
121
|
-
* return { allowed: true };
|
|
122
|
-
* }
|
|
123
|
-
*
|
|
124
|
-
* toMiddleware(operation) {
|
|
125
|
-
* return async (request, reply) => {
|
|
126
|
-
* const result = await this.can(request.user, operation, {
|
|
127
|
-
* document: request.document,
|
|
128
|
-
* body: request.body,
|
|
129
|
-
* params: request.params,
|
|
130
|
-
* query: request.query,
|
|
131
|
-
* });
|
|
132
|
-
*
|
|
133
|
-
* if (!result.allowed) {
|
|
134
|
-
* reply.code(403).send({ error: result.reason });
|
|
135
|
-
* }
|
|
136
|
-
*
|
|
137
|
-
* // Attach filters/fieldMask to request
|
|
138
|
-
* request.policyResult = result;
|
|
139
|
-
* };
|
|
140
|
-
* }
|
|
141
|
-
* }
|
|
142
|
-
* ```
|
|
143
|
-
*
|
|
144
|
-
* @example HIPAA Compliance
|
|
145
|
-
* ```typescript
|
|
146
|
-
* class HIPAAPolicyEngine implements PolicyEngine {
|
|
147
|
-
* can(user, operation, context) {
|
|
148
|
-
* // Check patient consent
|
|
149
|
-
* // Verify user certifications
|
|
150
|
-
* // Check data sensitivity level
|
|
151
|
-
* // Create audit log entry
|
|
152
|
-
*
|
|
153
|
-
* return {
|
|
154
|
-
* allowed: this.checkHIPAACompliance(user, operation, context),
|
|
155
|
-
* reason: 'HIPAA compliance check failed',
|
|
156
|
-
* metadata: {
|
|
157
|
-
* auditLog: this.createAuditEntry(user, operation),
|
|
158
|
-
* },
|
|
159
|
-
* };
|
|
160
|
-
* }
|
|
161
|
-
*
|
|
162
|
-
* toMiddleware(operation) {
|
|
163
|
-
* // HIPAA-specific middleware with audit logging
|
|
164
|
-
* }
|
|
165
|
-
* }
|
|
166
|
-
* ```
|
|
167
|
-
*/
|
|
168
|
-
interface PolicyEngine {
|
|
169
|
-
/**
|
|
170
|
-
* Check if user can perform operation
|
|
171
|
-
*
|
|
172
|
-
* @param user - User object from request (request.user)
|
|
173
|
-
* @param operation - Operation name (list, get, create, update, delete, custom)
|
|
174
|
-
* @param context - Additional context (document, body, params, query, etc.)
|
|
175
|
-
* @returns Policy result with allowed/denied and optional filters/fieldMask
|
|
176
|
-
*
|
|
177
|
-
* @example
|
|
178
|
-
* ```typescript
|
|
179
|
-
* const result = await policy.can(request.user, 'update', {
|
|
180
|
-
* document: existingDocument,
|
|
181
|
-
* body: request.body,
|
|
182
|
-
* });
|
|
183
|
-
*
|
|
184
|
-
* if (!result.allowed) {
|
|
185
|
-
* throw new Error(result.reason);
|
|
186
|
-
* }
|
|
187
|
-
* ```
|
|
188
|
-
*/
|
|
189
|
-
can(user: unknown, operation: string, context?: PolicyContext): PolicyResult | Promise<PolicyResult>;
|
|
190
|
-
/**
|
|
191
|
-
* Generate Fastify middleware for this policy
|
|
192
|
-
*
|
|
193
|
-
* Called during route registration to create preHandler middleware.
|
|
194
|
-
* Middleware should:
|
|
195
|
-
* 1. Call can() with request context
|
|
196
|
-
* 2. Return 403 if denied
|
|
197
|
-
* 3. Attach result to request for downstream use
|
|
198
|
-
*
|
|
199
|
-
* @param operation - Operation name (list, get, create, update, delete)
|
|
200
|
-
* @returns Fastify preHandler middleware
|
|
201
|
-
*
|
|
202
|
-
* @example
|
|
203
|
-
* ```typescript
|
|
204
|
-
* const middleware = policy.toMiddleware('update');
|
|
205
|
-
* fastify.put('/products/:id', {
|
|
206
|
-
* preHandler: [authenticate, middleware],
|
|
207
|
-
* }, handler);
|
|
208
|
-
* ```
|
|
209
|
-
*/
|
|
210
|
-
toMiddleware(operation: string): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Policy factory function signature
|
|
214
|
-
*
|
|
215
|
-
* Policies are typically created via factory functions that accept configuration.
|
|
216
|
-
*
|
|
217
|
-
* @example
|
|
218
|
-
* ```typescript
|
|
219
|
-
* export function definePolicy(config: PolicyConfig): PolicyEngine {
|
|
220
|
-
* return new MyPolicyEngine(config);
|
|
221
|
-
* }
|
|
222
|
-
*
|
|
223
|
-
* // Usage
|
|
224
|
-
* const productPolicy = definePolicy({
|
|
225
|
-
* resource: 'product',
|
|
226
|
-
* roles: { list: ['user'], create: ['admin'] },
|
|
227
|
-
* ownership: { field: 'userId', operations: ['update', 'delete'] },
|
|
228
|
-
* });
|
|
229
|
-
* ```
|
|
230
|
-
*/
|
|
231
|
-
type PolicyFactory<TConfig = unknown> = (config: TConfig) => PolicyEngine;
|
|
232
|
-
/**
|
|
233
|
-
* Extended Fastify request with policy result
|
|
234
|
-
*/
|
|
235
|
-
/**
|
|
236
|
-
* Access control statement
|
|
237
|
-
*
|
|
238
|
-
* Maps to Better Auth's organization permission model
|
|
239
|
-
* where permissions are defined as resource + action pairs.
|
|
240
|
-
*/
|
|
241
|
-
interface AccessControlStatement {
|
|
242
|
-
/** Resource name (e.g., 'product', 'order') */
|
|
243
|
-
resource: string;
|
|
244
|
-
/** Allowed actions on this resource */
|
|
245
|
-
action: string[];
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Options for createAccessControlPolicy
|
|
249
|
-
*/
|
|
250
|
-
interface AccessControlPolicyOptions {
|
|
251
|
-
/** Permission statements defining resource-action pairs */
|
|
252
|
-
statements: AccessControlStatement[];
|
|
253
|
-
/**
|
|
254
|
-
* Optional async permission check against external source (e.g., org role permissions).
|
|
255
|
-
* Called when the static statements allow the action — use this for dynamic checks
|
|
256
|
-
* like verifying the user's org role actually grants the permission.
|
|
257
|
-
*
|
|
258
|
-
* @param userId - ID of the user
|
|
259
|
-
* @param resource - Resource being accessed
|
|
260
|
-
* @param action - Action being performed
|
|
261
|
-
* @returns Whether the user has the permission
|
|
262
|
-
*/
|
|
263
|
-
checkPermission?: (userId: string, resource: string, action: string) => Promise<boolean>;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Create a PermissionCheck from access control statements.
|
|
267
|
-
*
|
|
268
|
-
* Maps Better Auth's statement-based access control model to Arc's
|
|
269
|
-
* PermissionCheck function, which can be used directly in resource permissions.
|
|
270
|
-
*
|
|
271
|
-
* The returned PermissionCheck:
|
|
272
|
-
* 1. Looks up the resource + action in the statements list
|
|
273
|
-
* 2. If no matching statement exists, denies access
|
|
274
|
-
* 3. If a matching statement exists and `checkPermission` is provided,
|
|
275
|
-
* calls it for dynamic verification (e.g., check org role)
|
|
276
|
-
* 4. If `checkPermission` is not provided, allows access based on static statements
|
|
277
|
-
*
|
|
278
|
-
* @example Static statements only
|
|
279
|
-
* ```typescript
|
|
280
|
-
* import { createAccessControlPolicy } from '@classytic/arc/policies';
|
|
281
|
-
*
|
|
282
|
-
* const editorPermissions = createAccessControlPolicy({
|
|
283
|
-
* statements: [
|
|
284
|
-
* { resource: 'product', action: ['create', 'update'] },
|
|
285
|
-
* { resource: 'order', action: ['read'] },
|
|
286
|
-
* ],
|
|
287
|
-
* });
|
|
288
|
-
*
|
|
289
|
-
* // Use in resource config
|
|
290
|
-
* defineResource({
|
|
291
|
-
* name: 'product',
|
|
292
|
-
* permissions: {
|
|
293
|
-
* create: editorPermissions,
|
|
294
|
-
* update: editorPermissions,
|
|
295
|
-
* },
|
|
296
|
-
* });
|
|
297
|
-
* ```
|
|
298
|
-
*
|
|
299
|
-
* @example With dynamic permission check (Better Auth org roles)
|
|
300
|
-
* ```typescript
|
|
301
|
-
* const policy = createAccessControlPolicy({
|
|
302
|
-
* statements: [
|
|
303
|
-
* { resource: 'product', action: ['create', 'update'] },
|
|
304
|
-
* { resource: 'order', action: ['read'] },
|
|
305
|
-
* ],
|
|
306
|
-
* checkPermission: async (userId, resource, action) => {
|
|
307
|
-
* return hasOrgPermission(userId, resource, action);
|
|
308
|
-
* },
|
|
309
|
-
* });
|
|
310
|
-
* ```
|
|
311
|
-
*/
|
|
312
|
-
declare function createAccessControlPolicy(options: AccessControlPolicyOptions): PermissionCheck;
|
|
313
|
-
declare module "fastify" {
|
|
314
|
-
interface FastifyRequest {
|
|
315
|
-
policyResult?: PolicyResult;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
//#endregion
|
|
319
|
-
//#region src/policies/helpers.d.ts
|
|
320
|
-
/**
|
|
321
|
-
* Helper to create Fastify middleware from any PolicyEngine implementation
|
|
322
|
-
*
|
|
323
|
-
* This is a convenience function that provides a standard middleware pattern.
|
|
324
|
-
* Most policies can use this instead of implementing toMiddleware() manually.
|
|
325
|
-
*
|
|
326
|
-
* @param policy - Policy engine instance
|
|
327
|
-
* @param operation - Operation name (list, get, create, update, delete)
|
|
328
|
-
* @returns Fastify preHandler middleware
|
|
329
|
-
*
|
|
330
|
-
* @example
|
|
331
|
-
* ```typescript
|
|
332
|
-
* class SimplePolicy implements PolicyEngine {
|
|
333
|
-
* can(user, operation) {
|
|
334
|
-
* return { allowed: user.isActive };
|
|
335
|
-
* }
|
|
336
|
-
*
|
|
337
|
-
* toMiddleware(operation) {
|
|
338
|
-
* return createPolicyMiddleware(this, operation);
|
|
339
|
-
* }
|
|
340
|
-
* }
|
|
341
|
-
* ```
|
|
342
|
-
*/
|
|
343
|
-
declare function createPolicyMiddleware(policy: PolicyEngine, operation: string): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
344
|
-
/**
|
|
345
|
-
* Combine multiple policies with AND logic
|
|
346
|
-
*
|
|
347
|
-
* All policies must allow the operation for it to succeed.
|
|
348
|
-
* First denial stops evaluation and returns the denial reason.
|
|
349
|
-
*
|
|
350
|
-
* @param policies - Array of policy engines to combine
|
|
351
|
-
* @returns Combined policy engine
|
|
352
|
-
*
|
|
353
|
-
* @example
|
|
354
|
-
* ```typescript
|
|
355
|
-
* const combinedPolicy = combinePolicies(
|
|
356
|
-
* rbacPolicy, // Must have correct role
|
|
357
|
-
* ownershipPolicy, // Must own the resource
|
|
358
|
-
* auditPolicy, // Logs access for compliance
|
|
359
|
-
* );
|
|
360
|
-
*
|
|
361
|
-
* // All three policies must pass for the operation to succeed
|
|
362
|
-
* const result = await combinedPolicy.can(user, 'update', context);
|
|
363
|
-
* ```
|
|
364
|
-
*
|
|
365
|
-
* @example Multi-tenant + RBAC
|
|
366
|
-
* ```typescript
|
|
367
|
-
* const policy = combinePolicies(
|
|
368
|
-
* definePolicy({ tenant: { field: 'organizationId' } }),
|
|
369
|
-
* definePolicy({ roles: { update: ['admin', 'editor'] } }),
|
|
370
|
-
* );
|
|
371
|
-
* ```
|
|
372
|
-
*/
|
|
373
|
-
declare function combinePolicies(...policies: PolicyEngine[]): PolicyEngine;
|
|
374
|
-
/**
|
|
375
|
-
* Combine multiple policies with OR logic
|
|
376
|
-
*
|
|
377
|
-
* At least one policy must allow the operation for it to succeed.
|
|
378
|
-
* If all policies deny, returns the first denial reason.
|
|
379
|
-
*
|
|
380
|
-
* @param policies - Array of policy engines to combine
|
|
381
|
-
* @returns Combined policy engine
|
|
382
|
-
*
|
|
383
|
-
* @example
|
|
384
|
-
* ```typescript
|
|
385
|
-
* const policy = anyPolicy(
|
|
386
|
-
* ownerPolicy, // User owns the resource
|
|
387
|
-
* adminPolicy, // OR user is admin
|
|
388
|
-
* publicPolicy, // OR resource is public
|
|
389
|
-
* );
|
|
390
|
-
*
|
|
391
|
-
* // Any one of these policies passing allows the operation
|
|
392
|
-
* ```
|
|
393
|
-
*/
|
|
394
|
-
declare function anyPolicy(...policies: PolicyEngine[]): PolicyEngine;
|
|
395
|
-
/**
|
|
396
|
-
* Create a pass-through policy that always allows
|
|
397
|
-
*
|
|
398
|
-
* Useful for testing or for routes that don't need authorization.
|
|
399
|
-
*
|
|
400
|
-
* @example
|
|
401
|
-
* ```typescript
|
|
402
|
-
* const policy = allowAll();
|
|
403
|
-
* const result = await policy.can(user, 'any-operation');
|
|
404
|
-
* // result.allowed === true
|
|
405
|
-
* ```
|
|
406
|
-
*/
|
|
407
|
-
declare function allowAll(): PolicyEngine;
|
|
408
|
-
/**
|
|
409
|
-
* Create a policy that always denies
|
|
410
|
-
*
|
|
411
|
-
* Useful for explicitly blocking operations or for testing.
|
|
412
|
-
*
|
|
413
|
-
* @param reason - Denial reason
|
|
414
|
-
*
|
|
415
|
-
* @example
|
|
416
|
-
* ```typescript
|
|
417
|
-
* const policy = denyAll('This resource is deprecated');
|
|
418
|
-
* const result = await policy.can(user, 'any-operation');
|
|
419
|
-
* // result.allowed === false
|
|
420
|
-
* // result.reason === 'This resource is deprecated'
|
|
421
|
-
* ```
|
|
422
|
-
*/
|
|
423
|
-
declare function denyAll(reason?: string): PolicyEngine;
|
|
424
|
-
//#endregion
|
|
425
|
-
export { type AccessControlPolicyOptions, type AccessControlStatement, type PolicyContext, type PolicyEngine, type PolicyFactory, type PolicyResult, allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };
|