ai-database 2.0.2 → 2.1.1
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/CHANGELOG.md +36 -0
- package/dist/actions.d.ts +247 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +260 -0
- package/dist/actions.js.map +1 -0
- package/dist/ai-promise-db.d.ts +34 -2
- package/dist/ai-promise-db.d.ts.map +1 -1
- package/dist/ai-promise-db.js +511 -66
- package/dist/ai-promise-db.js.map +1 -1
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/events.d.ts +153 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +154 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/memory-provider.d.ts +144 -2
- package/dist/memory-provider.d.ts.map +1 -1
- package/dist/memory-provider.js +569 -13
- package/dist/memory-provider.js.map +1 -1
- package/dist/schema/cascade.d.ts +96 -0
- package/dist/schema/cascade.d.ts.map +1 -0
- package/dist/schema/cascade.js +528 -0
- package/dist/schema/cascade.js.map +1 -0
- package/dist/schema/index.d.ts +197 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +1211 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/parse.d.ts +225 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +732 -0
- package/dist/schema/parse.js.map +1 -0
- package/dist/schema/provider.d.ts +176 -0
- package/dist/schema/provider.d.ts.map +1 -0
- package/dist/schema/provider.js +258 -0
- package/dist/schema/provider.js.map +1 -0
- package/dist/schema/resolve.d.ts +87 -0
- package/dist/schema/resolve.d.ts.map +1 -0
- package/dist/schema/resolve.js +474 -0
- package/dist/schema/resolve.js.map +1 -0
- package/dist/schema/semantic.d.ts +53 -0
- package/dist/schema/semantic.d.ts.map +1 -0
- package/dist/schema/semantic.js +247 -0
- package/dist/schema/semantic.js.map +1 -0
- package/dist/schema/types.d.ts +528 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +9 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema.d.ts +24 -867
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +41 -1124
- package/dist/schema.js.map +1 -1
- package/dist/semantic.d.ts +175 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +338 -0
- package/dist/semantic.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +13 -4
- package/.turbo/turbo-build.log +0 -5
- package/TESTING.md +0 -410
- package/TEST_SUMMARY.md +0 -250
- package/TODO.md +0 -128
- package/src/ai-promise-db.ts +0 -1243
- package/src/authorization.ts +0 -1102
- package/src/durable-clickhouse.ts +0 -596
- package/src/durable-promise.ts +0 -582
- package/src/execution-queue.ts +0 -608
- package/src/index.test.ts +0 -868
- package/src/index.ts +0 -337
- package/src/linguistic.ts +0 -404
- package/src/memory-provider.test.ts +0 -1036
- package/src/memory-provider.ts +0 -1119
- package/src/schema.test.ts +0 -1254
- package/src/schema.ts +0 -2296
- package/src/tests.ts +0 -725
- package/src/types.ts +0 -1177
- package/test/README.md +0 -153
- package/test/edge-cases.test.ts +0 -646
- package/test/provider-resolution.test.ts +0 -402
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
package/src/authorization.ts
DELETED
|
@@ -1,1102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorization Primitives (FGA/RBAC)
|
|
3
|
-
*
|
|
4
|
-
* Based on WorkOS FGA design - Fine-Grained Authorization that:
|
|
5
|
-
* - Extends RBAC with resource-scoped, hierarchical permissions
|
|
6
|
-
* - Uses Subjects, Resources, Roles, Permissions, Assignments
|
|
7
|
-
* - Supports automatic inheritance through resource hierarchy
|
|
8
|
-
* - Integrates with Noun/Verb for action authorization
|
|
9
|
-
*
|
|
10
|
-
* @see https://workos.com/docs/fga
|
|
11
|
-
* @packageDocumentation
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { Noun, Verb } from './schema.js'
|
|
15
|
-
|
|
16
|
-
// =============================================================================
|
|
17
|
-
// Core FGA Primitives
|
|
18
|
-
// =============================================================================
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Subject - who is requesting access
|
|
22
|
-
*
|
|
23
|
-
* Can be a user, group, service account, AI agent, or any entity
|
|
24
|
-
* that can be granted permissions.
|
|
25
|
-
*/
|
|
26
|
-
export interface Subject {
|
|
27
|
-
/** Subject type (user, group, service, agent) */
|
|
28
|
-
type: string
|
|
29
|
-
|
|
30
|
-
/** Unique identifier within the type */
|
|
31
|
-
id: string
|
|
32
|
-
|
|
33
|
-
/** Optional display name */
|
|
34
|
-
name?: string
|
|
35
|
-
|
|
36
|
-
/** Subject metadata */
|
|
37
|
-
metadata?: Record<string, unknown>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Subject types
|
|
42
|
-
*/
|
|
43
|
-
export type SubjectType =
|
|
44
|
-
| 'user' // Human user
|
|
45
|
-
| 'group' // Group of users
|
|
46
|
-
| 'team' // Team (organizational unit)
|
|
47
|
-
| 'service' // Service account
|
|
48
|
-
| 'agent' // AI agent
|
|
49
|
-
| 'role' // Role (for role inheritance)
|
|
50
|
-
| string // Custom subject types
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Resource - what is being accessed
|
|
54
|
-
*
|
|
55
|
-
* Any entity in the system that can have permissions.
|
|
56
|
-
* Resources form a hierarchy (up to 5 levels).
|
|
57
|
-
*/
|
|
58
|
-
export interface Resource {
|
|
59
|
-
/** Resource type (maps to Noun) */
|
|
60
|
-
type: string
|
|
61
|
-
|
|
62
|
-
/** Unique identifier */
|
|
63
|
-
id: string
|
|
64
|
-
|
|
65
|
-
/** Parent resource (for hierarchy) */
|
|
66
|
-
parent?: ResourceRef
|
|
67
|
-
|
|
68
|
-
/** Resource metadata */
|
|
69
|
-
metadata?: Record<string, unknown>
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Resource reference (lightweight)
|
|
74
|
-
*/
|
|
75
|
-
export interface ResourceRef {
|
|
76
|
-
type: string
|
|
77
|
-
id: string
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Resource type definition
|
|
82
|
-
*/
|
|
83
|
-
export interface ResourceType {
|
|
84
|
-
/** Type name (should match a Noun) */
|
|
85
|
-
name: string
|
|
86
|
-
|
|
87
|
-
/** Human-readable description */
|
|
88
|
-
description?: string
|
|
89
|
-
|
|
90
|
-
/** Parent type in hierarchy (null = root) */
|
|
91
|
-
parentType?: string
|
|
92
|
-
|
|
93
|
-
/** Actions available on this resource type */
|
|
94
|
-
actions?: string[]
|
|
95
|
-
|
|
96
|
-
/** Whether this is a high-cardinality type (kept local, not synced) */
|
|
97
|
-
local?: boolean
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// =============================================================================
|
|
101
|
-
// Role & Permission
|
|
102
|
-
// =============================================================================
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Permission - what action can be performed on what resource type
|
|
106
|
-
*/
|
|
107
|
-
export interface Permission {
|
|
108
|
-
/** Permission name (e.g., 'read', 'write', 'admin') */
|
|
109
|
-
name: string
|
|
110
|
-
|
|
111
|
-
/** Description */
|
|
112
|
-
description?: string
|
|
113
|
-
|
|
114
|
-
/** Resource type this permission applies to */
|
|
115
|
-
resourceType: string
|
|
116
|
-
|
|
117
|
-
/** Actions granted (maps to Verb actions) */
|
|
118
|
-
actions: string[]
|
|
119
|
-
|
|
120
|
-
/** Whether this permission extends to child resource types */
|
|
121
|
-
inheritable?: boolean
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Role - a named collection of permissions
|
|
126
|
-
*
|
|
127
|
-
* Roles are scoped to resource types. A "Workspace Admin" role
|
|
128
|
-
* only makes sense on Workspace resources.
|
|
129
|
-
*/
|
|
130
|
-
export interface Role {
|
|
131
|
-
/** Role identifier */
|
|
132
|
-
id: string
|
|
133
|
-
|
|
134
|
-
/** Display name */
|
|
135
|
-
name: string
|
|
136
|
-
|
|
137
|
-
/** Description */
|
|
138
|
-
description?: string
|
|
139
|
-
|
|
140
|
-
/** Resource type this role is scoped to */
|
|
141
|
-
resourceType: string
|
|
142
|
-
|
|
143
|
-
/** Permissions included in this role */
|
|
144
|
-
permissions: Permission[]
|
|
145
|
-
|
|
146
|
-
/** Parent roles (for role inheritance) */
|
|
147
|
-
inherits?: string[]
|
|
148
|
-
|
|
149
|
-
/** Metadata */
|
|
150
|
-
metadata?: Record<string, unknown>
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Standard role levels (can be customized)
|
|
155
|
-
*/
|
|
156
|
-
export type RoleLevel =
|
|
157
|
-
| 'owner' // Full control, can delete
|
|
158
|
-
| 'admin' // Full control, cannot delete
|
|
159
|
-
| 'editor' // Can modify
|
|
160
|
-
| 'viewer' // Read-only
|
|
161
|
-
| 'guest' // Limited read
|
|
162
|
-
| string // Custom levels
|
|
163
|
-
|
|
164
|
-
// =============================================================================
|
|
165
|
-
// Assignment (Warrant)
|
|
166
|
-
// =============================================================================
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Assignment - binds a subject to a role on a resource
|
|
170
|
-
*
|
|
171
|
-
* This is the core authorization tuple: (subject, role, resource)
|
|
172
|
-
* Also called a "warrant" in some FGA systems.
|
|
173
|
-
*/
|
|
174
|
-
export interface Assignment {
|
|
175
|
-
/** Unique assignment ID */
|
|
176
|
-
id: string
|
|
177
|
-
|
|
178
|
-
/** Who is granted access */
|
|
179
|
-
subject: Subject
|
|
180
|
-
|
|
181
|
-
/** What role is granted */
|
|
182
|
-
role: string
|
|
183
|
-
|
|
184
|
-
/** On what resource */
|
|
185
|
-
resource: ResourceRef
|
|
186
|
-
|
|
187
|
-
/** When the assignment was created */
|
|
188
|
-
createdAt: Date
|
|
189
|
-
|
|
190
|
-
/** Who created the assignment */
|
|
191
|
-
createdBy?: Subject
|
|
192
|
-
|
|
193
|
-
/** Optional expiration */
|
|
194
|
-
expiresAt?: Date
|
|
195
|
-
|
|
196
|
-
/** Assignment metadata */
|
|
197
|
-
metadata?: Record<string, unknown>
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Assignment input (for creating assignments)
|
|
202
|
-
*/
|
|
203
|
-
export interface AssignmentInput {
|
|
204
|
-
subject: Subject | string // Can use "user:123" format
|
|
205
|
-
role: string
|
|
206
|
-
resource: ResourceRef | string // Can use "workspace:456" format
|
|
207
|
-
expiresAt?: Date
|
|
208
|
-
metadata?: Record<string, unknown>
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// =============================================================================
|
|
212
|
-
// Authorization Check
|
|
213
|
-
// =============================================================================
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Authorization check request
|
|
217
|
-
*/
|
|
218
|
-
export interface AuthzCheckRequest {
|
|
219
|
-
/** Who is requesting */
|
|
220
|
-
subject: Subject | string
|
|
221
|
-
|
|
222
|
-
/** What action */
|
|
223
|
-
action: string
|
|
224
|
-
|
|
225
|
-
/** On what resource */
|
|
226
|
-
resource: ResourceRef | string
|
|
227
|
-
|
|
228
|
-
/** Additional context */
|
|
229
|
-
context?: Record<string, unknown>
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Authorization check result
|
|
234
|
-
*/
|
|
235
|
-
export interface AuthzCheckResult {
|
|
236
|
-
/** Whether access is allowed */
|
|
237
|
-
allowed: boolean
|
|
238
|
-
|
|
239
|
-
/** Why (for debugging/audit) */
|
|
240
|
-
reason?: string
|
|
241
|
-
|
|
242
|
-
/** Which assignment granted access (if allowed) */
|
|
243
|
-
assignment?: Assignment
|
|
244
|
-
|
|
245
|
-
/** Check latency in ms */
|
|
246
|
-
latencyMs?: number
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Batch authorization check
|
|
251
|
-
*/
|
|
252
|
-
export interface AuthzBatchCheckRequest {
|
|
253
|
-
checks: AuthzCheckRequest[]
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export interface AuthzBatchCheckResult {
|
|
257
|
-
results: AuthzCheckResult[]
|
|
258
|
-
latencyMs: number
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// =============================================================================
|
|
262
|
-
// Resource Hierarchy
|
|
263
|
-
// =============================================================================
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Resource hierarchy definition
|
|
267
|
-
*
|
|
268
|
-
* Defines the parent-child relationships between resource types.
|
|
269
|
-
* Permissions can flow down through the hierarchy.
|
|
270
|
-
*
|
|
271
|
-
* @example
|
|
272
|
-
* ```ts
|
|
273
|
-
* const hierarchy: ResourceHierarchy = {
|
|
274
|
-
* levels: [
|
|
275
|
-
* { type: 'organization', depth: 0 },
|
|
276
|
-
* { type: 'workspace', depth: 1, parentType: 'organization' },
|
|
277
|
-
* { type: 'project', depth: 2, parentType: 'workspace' },
|
|
278
|
-
* { type: 'document', depth: 3, parentType: 'project' },
|
|
279
|
-
* ],
|
|
280
|
-
* maxDepth: 5,
|
|
281
|
-
* }
|
|
282
|
-
* ```
|
|
283
|
-
*/
|
|
284
|
-
export interface ResourceHierarchy {
|
|
285
|
-
/** Hierarchy levels */
|
|
286
|
-
levels: ResourceType[]
|
|
287
|
-
|
|
288
|
-
/** Maximum depth (WorkOS supports up to 5) */
|
|
289
|
-
maxDepth: number
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Common SaaS resource hierarchies
|
|
294
|
-
*/
|
|
295
|
-
export const StandardHierarchies = {
|
|
296
|
-
/** Organization → Workspace → Project → Resource */
|
|
297
|
-
saas: {
|
|
298
|
-
levels: [
|
|
299
|
-
{ name: 'organization', description: 'Top-level organization' },
|
|
300
|
-
{ name: 'workspace', description: 'Workspace within org', parentType: 'organization' },
|
|
301
|
-
{ name: 'project', description: 'Project within workspace', parentType: 'workspace' },
|
|
302
|
-
{ name: 'resource', description: 'Resource within project', parentType: 'project' },
|
|
303
|
-
],
|
|
304
|
-
maxDepth: 4,
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
/** Organization → Team → Repository */
|
|
308
|
-
devtools: {
|
|
309
|
-
levels: [
|
|
310
|
-
{ name: 'organization', description: 'Top-level organization' },
|
|
311
|
-
{ name: 'team', description: 'Team within org', parentType: 'organization' },
|
|
312
|
-
{ name: 'repository', description: 'Repository owned by team', parentType: 'team' },
|
|
313
|
-
],
|
|
314
|
-
maxDepth: 3,
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
/** Account → Folder → Document */
|
|
318
|
-
documents: {
|
|
319
|
-
levels: [
|
|
320
|
-
{ name: 'account', description: 'User account' },
|
|
321
|
-
{ name: 'folder', description: 'Folder in account', parentType: 'account' },
|
|
322
|
-
{ name: 'document', description: 'Document in folder', parentType: 'folder' },
|
|
323
|
-
],
|
|
324
|
-
maxDepth: 3,
|
|
325
|
-
},
|
|
326
|
-
} as const satisfies Record<string, ResourceHierarchy>
|
|
327
|
-
|
|
328
|
-
// =============================================================================
|
|
329
|
-
// Role Definitions
|
|
330
|
-
// =============================================================================
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Standard permissions
|
|
334
|
-
*
|
|
335
|
-
* Beyond CRUD, we add `act` for domain-specific verbs (send, pay, publish, etc.)
|
|
336
|
-
*
|
|
337
|
-
* Permission levels:
|
|
338
|
-
* - read → view data (GET operations)
|
|
339
|
-
* - edit → modify data (PUT/PATCH operations)
|
|
340
|
-
* - act → perform actions/verbs (POST operations with side effects)
|
|
341
|
-
* - delete → remove (DELETE operations)
|
|
342
|
-
* - manage → all + role assignment
|
|
343
|
-
*/
|
|
344
|
-
export const StandardPermissions = {
|
|
345
|
-
create: (resourceType: string): Permission => ({
|
|
346
|
-
name: 'create',
|
|
347
|
-
description: `Create ${resourceType}`,
|
|
348
|
-
resourceType,
|
|
349
|
-
actions: ['create'],
|
|
350
|
-
inheritable: true,
|
|
351
|
-
}),
|
|
352
|
-
|
|
353
|
-
read: (resourceType: string): Permission => ({
|
|
354
|
-
name: 'read',
|
|
355
|
-
description: `Read ${resourceType}`,
|
|
356
|
-
resourceType,
|
|
357
|
-
actions: ['read', 'get', 'list', 'search', 'view'],
|
|
358
|
-
inheritable: true,
|
|
359
|
-
}),
|
|
360
|
-
|
|
361
|
-
edit: (resourceType: string): Permission => ({
|
|
362
|
-
name: 'edit',
|
|
363
|
-
description: `Edit ${resourceType}`,
|
|
364
|
-
resourceType,
|
|
365
|
-
actions: ['update', 'edit', 'modify', 'patch'],
|
|
366
|
-
inheritable: true,
|
|
367
|
-
}),
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Act - perform domain-specific verbs/actions
|
|
371
|
-
*
|
|
372
|
-
* This is for state transitions and side effects:
|
|
373
|
-
* - invoice.send, invoice.pay, invoice.void
|
|
374
|
-
* - document.publish, document.archive
|
|
375
|
-
* - order.fulfill, order.refund
|
|
376
|
-
*
|
|
377
|
-
* Can be scoped: act:* (all), act:send, act:pay, etc.
|
|
378
|
-
*/
|
|
379
|
-
act: (resourceType: string, verbs?: string[]): Permission => ({
|
|
380
|
-
name: 'act',
|
|
381
|
-
description: verbs
|
|
382
|
-
? `Perform ${verbs.join(', ')} on ${resourceType}`
|
|
383
|
-
: `Perform actions on ${resourceType}`,
|
|
384
|
-
resourceType,
|
|
385
|
-
actions: verbs || ['*'], // '*' means all verbs
|
|
386
|
-
inheritable: true,
|
|
387
|
-
}),
|
|
388
|
-
|
|
389
|
-
delete: (resourceType: string): Permission => ({
|
|
390
|
-
name: 'delete',
|
|
391
|
-
description: `Delete ${resourceType}`,
|
|
392
|
-
resourceType,
|
|
393
|
-
actions: ['delete', 'remove', 'destroy'],
|
|
394
|
-
inheritable: false, // Usually not inherited
|
|
395
|
-
}),
|
|
396
|
-
|
|
397
|
-
manage: (resourceType: string): Permission => ({
|
|
398
|
-
name: 'manage',
|
|
399
|
-
description: `Full management of ${resourceType}`,
|
|
400
|
-
resourceType,
|
|
401
|
-
actions: ['*'], // All actions
|
|
402
|
-
inheritable: true,
|
|
403
|
-
}),
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* @deprecated Use StandardPermissions instead
|
|
408
|
-
*/
|
|
409
|
-
export const CRUDPermissions = StandardPermissions
|
|
410
|
-
|
|
411
|
-
// =============================================================================
|
|
412
|
-
// Verb-Scoped Permissions (e.g., invoice.pay, document.publish)
|
|
413
|
-
// =============================================================================
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Create a verb-scoped permission
|
|
417
|
-
*
|
|
418
|
-
* @example
|
|
419
|
-
* ```ts
|
|
420
|
-
* // Single verb
|
|
421
|
-
* const canPay = verbPermission('invoice', 'pay')
|
|
422
|
-
* // { name: 'invoice.pay', actions: ['pay'], resourceType: 'invoice' }
|
|
423
|
-
*
|
|
424
|
-
* // Multiple verbs
|
|
425
|
-
* const canManagePayments = verbPermission('invoice', ['send', 'pay', 'void', 'refund'])
|
|
426
|
-
* ```
|
|
427
|
-
*/
|
|
428
|
-
export function verbPermission(
|
|
429
|
-
resourceType: string,
|
|
430
|
-
verbs: string | string[],
|
|
431
|
-
options?: { inheritable?: boolean; description?: string }
|
|
432
|
-
): Permission {
|
|
433
|
-
const verbList = Array.isArray(verbs) ? verbs : [verbs]
|
|
434
|
-
const name = verbList.length === 1
|
|
435
|
-
? `${resourceType}.${verbList[0]}`
|
|
436
|
-
: `${resourceType}.[${verbList.join(',')}]`
|
|
437
|
-
|
|
438
|
-
return {
|
|
439
|
-
name,
|
|
440
|
-
description: options?.description || `Can ${verbList.join(', ')} ${resourceType}`,
|
|
441
|
-
resourceType,
|
|
442
|
-
actions: verbList,
|
|
443
|
-
inheritable: options?.inheritable ?? true,
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Create permissions from a Noun's actions
|
|
449
|
-
*
|
|
450
|
-
* @example
|
|
451
|
-
* ```ts
|
|
452
|
-
* const invoicePerms = nounPermissions(InvoiceNoun)
|
|
453
|
-
* // Creates: invoice.create, invoice.send, invoice.pay, invoice.void, etc.
|
|
454
|
-
* ```
|
|
455
|
-
*/
|
|
456
|
-
export function nounPermissions(noun: Noun): Permission[] {
|
|
457
|
-
const resourceType = noun.singular
|
|
458
|
-
const permissions: Permission[] = []
|
|
459
|
-
|
|
460
|
-
// Standard CRUD
|
|
461
|
-
permissions.push(StandardPermissions.read(resourceType))
|
|
462
|
-
permissions.push(StandardPermissions.edit(resourceType))
|
|
463
|
-
permissions.push(StandardPermissions.delete(resourceType))
|
|
464
|
-
|
|
465
|
-
// Domain-specific verbs from noun.actions
|
|
466
|
-
if (noun.actions) {
|
|
467
|
-
for (const action of noun.actions) {
|
|
468
|
-
const verb = typeof action === 'string' ? action : action.action
|
|
469
|
-
// Skip standard CRUD verbs (already covered)
|
|
470
|
-
if (['create', 'read', 'update', 'delete', 'get', 'list'].includes(verb)) continue
|
|
471
|
-
|
|
472
|
-
permissions.push(verbPermission(resourceType, verb))
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return permissions
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Permission pattern matching
|
|
481
|
-
*
|
|
482
|
-
* Checks if an action matches a permission pattern.
|
|
483
|
-
*
|
|
484
|
-
* @example
|
|
485
|
-
* ```ts
|
|
486
|
-
* matchesPermission('pay', ['*']) // true - wildcard
|
|
487
|
-
* matchesPermission('pay', ['pay']) // true - exact
|
|
488
|
-
* matchesPermission('pay', ['send', 'pay']) // true - in list
|
|
489
|
-
* matchesPermission('pay', ['send']) // false
|
|
490
|
-
* ```
|
|
491
|
-
*/
|
|
492
|
-
export function matchesPermission(action: string, allowedActions: string[]): boolean {
|
|
493
|
-
if (allowedActions.includes('*')) return true
|
|
494
|
-
if (allowedActions.includes(action)) return true
|
|
495
|
-
|
|
496
|
-
// Check for prefix patterns like 'invoice.*' matching 'invoice.pay'
|
|
497
|
-
for (const pattern of allowedActions) {
|
|
498
|
-
if (pattern.endsWith('.*')) {
|
|
499
|
-
const prefix = pattern.slice(0, -2)
|
|
500
|
-
if (action.startsWith(prefix + '.')) return true
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return false
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Create standard roles for a resource type
|
|
509
|
-
*/
|
|
510
|
-
export function createStandardRoles(resourceType: string): Record<RoleLevel, Role> {
|
|
511
|
-
return {
|
|
512
|
-
owner: {
|
|
513
|
-
id: `${resourceType}:owner`,
|
|
514
|
-
name: 'Owner',
|
|
515
|
-
description: `Full control of ${resourceType}, including deletion and transfer`,
|
|
516
|
-
resourceType,
|
|
517
|
-
permissions: [
|
|
518
|
-
CRUDPermissions.manage(resourceType),
|
|
519
|
-
{
|
|
520
|
-
name: 'transfer',
|
|
521
|
-
description: 'Transfer ownership',
|
|
522
|
-
resourceType,
|
|
523
|
-
actions: ['transfer'],
|
|
524
|
-
inheritable: false,
|
|
525
|
-
},
|
|
526
|
-
],
|
|
527
|
-
},
|
|
528
|
-
|
|
529
|
-
admin: {
|
|
530
|
-
id: `${resourceType}:admin`,
|
|
531
|
-
name: 'Admin',
|
|
532
|
-
description: `Administrative access to ${resourceType}`,
|
|
533
|
-
resourceType,
|
|
534
|
-
permissions: [
|
|
535
|
-
StandardPermissions.create(resourceType),
|
|
536
|
-
StandardPermissions.read(resourceType),
|
|
537
|
-
StandardPermissions.edit(resourceType),
|
|
538
|
-
StandardPermissions.act(resourceType),
|
|
539
|
-
StandardPermissions.delete(resourceType),
|
|
540
|
-
],
|
|
541
|
-
},
|
|
542
|
-
|
|
543
|
-
editor: {
|
|
544
|
-
id: `${resourceType}:editor`,
|
|
545
|
-
name: 'Editor',
|
|
546
|
-
description: `Can edit ${resourceType}`,
|
|
547
|
-
resourceType,
|
|
548
|
-
permissions: [
|
|
549
|
-
StandardPermissions.read(resourceType),
|
|
550
|
-
StandardPermissions.edit(resourceType),
|
|
551
|
-
StandardPermissions.act(resourceType),
|
|
552
|
-
],
|
|
553
|
-
},
|
|
554
|
-
|
|
555
|
-
viewer: {
|
|
556
|
-
id: `${resourceType}:viewer`,
|
|
557
|
-
name: 'Viewer',
|
|
558
|
-
description: `Read-only access to ${resourceType}`,
|
|
559
|
-
resourceType,
|
|
560
|
-
permissions: [
|
|
561
|
-
StandardPermissions.read(resourceType),
|
|
562
|
-
],
|
|
563
|
-
},
|
|
564
|
-
|
|
565
|
-
guest: {
|
|
566
|
-
id: `${resourceType}:guest`,
|
|
567
|
-
name: 'Guest',
|
|
568
|
-
description: `Limited access to ${resourceType}`,
|
|
569
|
-
resourceType,
|
|
570
|
-
permissions: [
|
|
571
|
-
{
|
|
572
|
-
name: 'view',
|
|
573
|
-
description: 'View basic info',
|
|
574
|
-
resourceType,
|
|
575
|
-
actions: ['get'],
|
|
576
|
-
inheritable: false,
|
|
577
|
-
},
|
|
578
|
-
],
|
|
579
|
-
},
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// =============================================================================
|
|
584
|
-
// Authorization Schema (integrates with Noun)
|
|
585
|
-
// =============================================================================
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Authorization schema for a Noun
|
|
589
|
-
*
|
|
590
|
-
* Extends a Noun with authorization metadata.
|
|
591
|
-
*/
|
|
592
|
-
export interface AuthorizedNoun extends Noun {
|
|
593
|
-
/** Resource type configuration */
|
|
594
|
-
authorization?: {
|
|
595
|
-
/** Parent resource type in hierarchy */
|
|
596
|
-
parentType?: string
|
|
597
|
-
|
|
598
|
-
/** Available roles for this resource type */
|
|
599
|
-
roles?: Role[]
|
|
600
|
-
|
|
601
|
-
/** Custom permissions beyond CRUD */
|
|
602
|
-
permissions?: Permission[]
|
|
603
|
-
|
|
604
|
-
/** Whether this is a high-cardinality type */
|
|
605
|
-
local?: boolean
|
|
606
|
-
|
|
607
|
-
/** Default role for new resources */
|
|
608
|
-
defaultRole?: string
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* Add authorization to a Noun
|
|
614
|
-
*/
|
|
615
|
-
export function authorizeNoun(
|
|
616
|
-
noun: Noun,
|
|
617
|
-
config: AuthorizedNoun['authorization']
|
|
618
|
-
): AuthorizedNoun {
|
|
619
|
-
return {
|
|
620
|
-
...noun,
|
|
621
|
-
authorization: config,
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// =============================================================================
|
|
626
|
-
// Authorization Engine Interface
|
|
627
|
-
// =============================================================================
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Authorization engine interface
|
|
631
|
-
*
|
|
632
|
-
* Implemented by providers (WorkOS, in-memory, custom).
|
|
633
|
-
*/
|
|
634
|
-
export interface AuthorizationEngine {
|
|
635
|
-
// Resource management
|
|
636
|
-
createResource(resource: Resource): Promise<Resource>
|
|
637
|
-
getResource(ref: ResourceRef): Promise<Resource | null>
|
|
638
|
-
deleteResource(ref: ResourceRef): Promise<void>
|
|
639
|
-
listResources(type: string, parentRef?: ResourceRef): Promise<Resource[]>
|
|
640
|
-
|
|
641
|
-
// Assignment management
|
|
642
|
-
assign(input: AssignmentInput): Promise<Assignment>
|
|
643
|
-
unassign(assignmentId: string): Promise<void>
|
|
644
|
-
getAssignment(id: string): Promise<Assignment | null>
|
|
645
|
-
listAssignments(filter: {
|
|
646
|
-
subject?: Subject
|
|
647
|
-
role?: string
|
|
648
|
-
resource?: ResourceRef
|
|
649
|
-
}): Promise<Assignment[]>
|
|
650
|
-
|
|
651
|
-
// Authorization checks
|
|
652
|
-
check(request: AuthzCheckRequest): Promise<AuthzCheckResult>
|
|
653
|
-
batchCheck(request: AuthzBatchCheckRequest): Promise<AuthzBatchCheckResult>
|
|
654
|
-
|
|
655
|
-
// Discovery
|
|
656
|
-
listSubjectsWithAccess(resource: ResourceRef, action?: string): Promise<Subject[]>
|
|
657
|
-
listResourcesForSubject(subject: Subject, resourceType: string, action?: string): Promise<Resource[]>
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// =============================================================================
|
|
661
|
-
// Helper Functions
|
|
662
|
-
// =============================================================================
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Parse subject string to Subject object
|
|
666
|
-
*
|
|
667
|
-
* @example
|
|
668
|
-
* ```ts
|
|
669
|
-
* parseSubject('user:123') // { type: 'user', id: '123' }
|
|
670
|
-
* parseSubject('group:admins') // { type: 'group', id: 'admins' }
|
|
671
|
-
* ```
|
|
672
|
-
*/
|
|
673
|
-
export function parseSubject(str: string): Subject {
|
|
674
|
-
const [type, id] = str.split(':')
|
|
675
|
-
if (!type || !id) {
|
|
676
|
-
throw new Error(`Invalid subject format: ${str}. Expected 'type:id'`)
|
|
677
|
-
}
|
|
678
|
-
return { type, id }
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Format Subject as string
|
|
683
|
-
*/
|
|
684
|
-
export function formatSubject(subject: Subject): string {
|
|
685
|
-
return `${subject.type}:${subject.id}`
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Parse resource string to ResourceRef
|
|
690
|
-
*
|
|
691
|
-
* @example
|
|
692
|
-
* ```ts
|
|
693
|
-
* parseResource('workspace:456') // { type: 'workspace', id: '456' }
|
|
694
|
-
* ```
|
|
695
|
-
*/
|
|
696
|
-
export function parseResource(str: string): ResourceRef {
|
|
697
|
-
const [type, id] = str.split(':')
|
|
698
|
-
if (!type || !id) {
|
|
699
|
-
throw new Error(`Invalid resource format: ${str}. Expected 'type:id'`)
|
|
700
|
-
}
|
|
701
|
-
return { type, id }
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Format ResourceRef as string
|
|
706
|
-
*/
|
|
707
|
-
export function formatResource(resource: ResourceRef): string {
|
|
708
|
-
return `${resource.type}:${resource.id}`
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Check if a subject matches another (for assignment matching)
|
|
713
|
-
*/
|
|
714
|
-
export function subjectMatches(a: Subject, b: Subject): boolean {
|
|
715
|
-
return a.type === b.type && a.id === b.id
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Check if a resource matches another
|
|
720
|
-
*/
|
|
721
|
-
export function resourceMatches(a: ResourceRef, b: ResourceRef): boolean {
|
|
722
|
-
return a.type === b.type && a.id === b.id
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// =============================================================================
|
|
726
|
-
// In-Memory Authorization Engine (for testing/development)
|
|
727
|
-
// =============================================================================
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* In-memory authorization engine
|
|
731
|
-
*
|
|
732
|
-
* Simple implementation for testing and development.
|
|
733
|
-
* For production, use WorkOS or a persistent provider.
|
|
734
|
-
*/
|
|
735
|
-
export class InMemoryAuthorizationEngine implements AuthorizationEngine {
|
|
736
|
-
private resources = new Map<string, Resource>()
|
|
737
|
-
private assignments = new Map<string, Assignment>()
|
|
738
|
-
private roles = new Map<string, Role>()
|
|
739
|
-
private hierarchy: ResourceHierarchy
|
|
740
|
-
|
|
741
|
-
constructor(config?: { hierarchy?: ResourceHierarchy; roles?: Role[] }) {
|
|
742
|
-
this.hierarchy = config?.hierarchy || StandardHierarchies.saas
|
|
743
|
-
if (config?.roles) {
|
|
744
|
-
for (const role of config.roles) {
|
|
745
|
-
this.roles.set(role.id, role)
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Resource management
|
|
751
|
-
async createResource(resource: Resource): Promise<Resource> {
|
|
752
|
-
const key = formatResource(resource)
|
|
753
|
-
this.resources.set(key, resource)
|
|
754
|
-
return resource
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
async getResource(ref: ResourceRef): Promise<Resource | null> {
|
|
758
|
-
return this.resources.get(formatResource(ref)) || null
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
async deleteResource(ref: ResourceRef): Promise<void> {
|
|
762
|
-
this.resources.delete(formatResource(ref))
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
async listResources(type: string, parentRef?: ResourceRef): Promise<Resource[]> {
|
|
766
|
-
const results: Resource[] = []
|
|
767
|
-
for (const resource of this.resources.values()) {
|
|
768
|
-
if (resource.type !== type) continue
|
|
769
|
-
if (parentRef && (!resource.parent || !resourceMatches(resource.parent, parentRef))) continue
|
|
770
|
-
results.push(resource)
|
|
771
|
-
}
|
|
772
|
-
return results
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Assignment management
|
|
776
|
-
async assign(input: AssignmentInput): Promise<Assignment> {
|
|
777
|
-
const subject = typeof input.subject === 'string'
|
|
778
|
-
? parseSubject(input.subject)
|
|
779
|
-
: input.subject
|
|
780
|
-
const resource = typeof input.resource === 'string'
|
|
781
|
-
? parseResource(input.resource)
|
|
782
|
-
: input.resource
|
|
783
|
-
|
|
784
|
-
const assignment: Assignment = {
|
|
785
|
-
id: `${formatSubject(subject)}:${input.role}:${formatResource(resource)}`,
|
|
786
|
-
subject,
|
|
787
|
-
role: input.role,
|
|
788
|
-
resource,
|
|
789
|
-
createdAt: new Date(),
|
|
790
|
-
expiresAt: input.expiresAt,
|
|
791
|
-
metadata: input.metadata,
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
this.assignments.set(assignment.id, assignment)
|
|
795
|
-
return assignment
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
async unassign(assignmentId: string): Promise<void> {
|
|
799
|
-
this.assignments.delete(assignmentId)
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
async getAssignment(id: string): Promise<Assignment | null> {
|
|
803
|
-
return this.assignments.get(id) || null
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
async listAssignments(filter: {
|
|
807
|
-
subject?: Subject
|
|
808
|
-
role?: string
|
|
809
|
-
resource?: ResourceRef
|
|
810
|
-
}): Promise<Assignment[]> {
|
|
811
|
-
const results: Assignment[] = []
|
|
812
|
-
for (const assignment of this.assignments.values()) {
|
|
813
|
-
if (filter.subject && !subjectMatches(assignment.subject, filter.subject)) continue
|
|
814
|
-
if (filter.role && assignment.role !== filter.role) continue
|
|
815
|
-
if (filter.resource && !resourceMatches(assignment.resource, filter.resource)) continue
|
|
816
|
-
results.push(assignment)
|
|
817
|
-
}
|
|
818
|
-
return results
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// Authorization checks
|
|
822
|
-
async check(request: AuthzCheckRequest): Promise<AuthzCheckResult> {
|
|
823
|
-
const start = Date.now()
|
|
824
|
-
const subject = typeof request.subject === 'string'
|
|
825
|
-
? parseSubject(request.subject)
|
|
826
|
-
: request.subject
|
|
827
|
-
const resource = typeof request.resource === 'string'
|
|
828
|
-
? parseResource(request.resource)
|
|
829
|
-
: request.resource
|
|
830
|
-
|
|
831
|
-
// Check direct assignments
|
|
832
|
-
const assignments = await this.listAssignments({ subject })
|
|
833
|
-
|
|
834
|
-
for (const assignment of assignments) {
|
|
835
|
-
// Check if assignment is on this resource or a parent
|
|
836
|
-
if (this.resourceInScope(resource, assignment.resource)) {
|
|
837
|
-
const role = this.roles.get(assignment.role)
|
|
838
|
-
if (role && this.roleGrantsAction(role, request.action, resource.type)) {
|
|
839
|
-
return {
|
|
840
|
-
allowed: true,
|
|
841
|
-
reason: `Granted by role '${role.name}' on ${formatResource(assignment.resource)}`,
|
|
842
|
-
assignment,
|
|
843
|
-
latencyMs: Date.now() - start,
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
return {
|
|
850
|
-
allowed: false,
|
|
851
|
-
reason: 'No matching assignment found',
|
|
852
|
-
latencyMs: Date.now() - start,
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
async batchCheck(request: AuthzBatchCheckRequest): Promise<AuthzBatchCheckResult> {
|
|
857
|
-
const start = Date.now()
|
|
858
|
-
const results = await Promise.all(request.checks.map(c => this.check(c)))
|
|
859
|
-
return {
|
|
860
|
-
results,
|
|
861
|
-
latencyMs: Date.now() - start,
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// Discovery
|
|
866
|
-
async listSubjectsWithAccess(resource: ResourceRef, action?: string): Promise<Subject[]> {
|
|
867
|
-
const subjects: Subject[] = []
|
|
868
|
-
const seen = new Set<string>()
|
|
869
|
-
|
|
870
|
-
for (const assignment of this.assignments.values()) {
|
|
871
|
-
if (!this.resourceInScope(resource, assignment.resource)) continue
|
|
872
|
-
|
|
873
|
-
const role = this.roles.get(assignment.role)
|
|
874
|
-
if (action && role && !this.roleGrantsAction(role, action, resource.type)) continue
|
|
875
|
-
|
|
876
|
-
const key = formatSubject(assignment.subject)
|
|
877
|
-
if (!seen.has(key)) {
|
|
878
|
-
seen.add(key)
|
|
879
|
-
subjects.push(assignment.subject)
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
return subjects
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
async listResourcesForSubject(
|
|
887
|
-
subject: Subject,
|
|
888
|
-
resourceType: string,
|
|
889
|
-
action?: string
|
|
890
|
-
): Promise<Resource[]> {
|
|
891
|
-
const resources: Resource[] = []
|
|
892
|
-
const assignments = await this.listAssignments({ subject })
|
|
893
|
-
|
|
894
|
-
for (const assignment of assignments) {
|
|
895
|
-
const role = this.roles.get(assignment.role)
|
|
896
|
-
if (!role) continue
|
|
897
|
-
if (action && !this.roleGrantsAction(role, action, resourceType)) continue
|
|
898
|
-
|
|
899
|
-
// Find all resources of the type that are in scope
|
|
900
|
-
for (const resource of this.resources.values()) {
|
|
901
|
-
if (resource.type !== resourceType) continue
|
|
902
|
-
if (this.resourceInScope(resource, assignment.resource)) {
|
|
903
|
-
resources.push(resource)
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
return resources
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// Helpers
|
|
912
|
-
private resourceInScope(target: ResourceRef, scope: ResourceRef): boolean {
|
|
913
|
-
// Same resource
|
|
914
|
-
if (resourceMatches(target, scope)) return true
|
|
915
|
-
|
|
916
|
-
// Check if scope is a parent of target
|
|
917
|
-
const targetResource = this.resources.get(formatResource(target))
|
|
918
|
-
if (!targetResource?.parent) return false
|
|
919
|
-
|
|
920
|
-
return this.resourceInScope(targetResource.parent, scope)
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
private roleGrantsAction(role: Role, action: string, resourceType: string): boolean {
|
|
924
|
-
for (const permission of role.permissions) {
|
|
925
|
-
// Check resource type match (or inheritable)
|
|
926
|
-
if (permission.resourceType !== resourceType && !permission.inheritable) continue
|
|
927
|
-
|
|
928
|
-
// Check action match
|
|
929
|
-
if (permission.actions.includes(action) || permission.actions.includes('*')) {
|
|
930
|
-
return true
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// Check inherited roles
|
|
935
|
-
if (role.inherits) {
|
|
936
|
-
for (const inheritedRoleId of role.inherits) {
|
|
937
|
-
const inheritedRole = this.roles.get(inheritedRoleId)
|
|
938
|
-
if (inheritedRole && this.roleGrantsAction(inheritedRole, action, resourceType)) {
|
|
939
|
-
return true
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
return false
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// Role management
|
|
948
|
-
registerRole(role: Role): void {
|
|
949
|
-
this.roles.set(role.id, role)
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
getRole(id: string): Role | undefined {
|
|
953
|
-
return this.roles.get(id)
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// =============================================================================
|
|
958
|
-
// Business Role Integration
|
|
959
|
-
// =============================================================================
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* Business role - organizational role (CEO, Engineer, Manager)
|
|
963
|
-
*
|
|
964
|
-
* Different from authorization Role - this represents a job function.
|
|
965
|
-
* Can be linked to authorization Roles for permissions.
|
|
966
|
-
*/
|
|
967
|
-
export interface BusinessRole {
|
|
968
|
-
/** Role identifier */
|
|
969
|
-
id: string
|
|
970
|
-
|
|
971
|
-
/** Display name (CEO, Software Engineer, Product Manager) */
|
|
972
|
-
name: string
|
|
973
|
-
|
|
974
|
-
/** Description */
|
|
975
|
-
description?: string
|
|
976
|
-
|
|
977
|
-
/** Department */
|
|
978
|
-
department?: string
|
|
979
|
-
|
|
980
|
-
/** Level in organization (1 = IC, 2 = Manager, 3 = Director, 4 = VP, 5 = C-level) */
|
|
981
|
-
level?: number
|
|
982
|
-
|
|
983
|
-
/** Reports to (parent role) */
|
|
984
|
-
reportsTo?: string
|
|
985
|
-
|
|
986
|
-
/** Responsibilities */
|
|
987
|
-
responsibilities?: string[]
|
|
988
|
-
|
|
989
|
-
/** Required skills */
|
|
990
|
-
skills?: string[]
|
|
991
|
-
|
|
992
|
-
/** Authorization roles this business role grants */
|
|
993
|
-
authorizationRoles?: string[]
|
|
994
|
-
|
|
995
|
-
/** Metadata */
|
|
996
|
-
metadata?: Record<string, unknown>
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* Link business roles to authorization roles
|
|
1001
|
-
*/
|
|
1002
|
-
export function linkBusinessRole(
|
|
1003
|
-
businessRole: BusinessRole,
|
|
1004
|
-
authRoles: string[]
|
|
1005
|
-
): BusinessRole {
|
|
1006
|
-
return {
|
|
1007
|
-
...businessRole,
|
|
1008
|
-
authorizationRoles: [
|
|
1009
|
-
...(businessRole.authorizationRoles || []),
|
|
1010
|
-
...authRoles,
|
|
1011
|
-
],
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// =============================================================================
|
|
1016
|
-
// Noun Definition for Role (makes Role a first-class entity)
|
|
1017
|
-
// =============================================================================
|
|
1018
|
-
|
|
1019
|
-
/**
|
|
1020
|
-
* Role as a Noun - can be stored in ai-database
|
|
1021
|
-
*/
|
|
1022
|
-
export const RoleNoun: Noun = {
|
|
1023
|
-
singular: 'role',
|
|
1024
|
-
plural: 'roles',
|
|
1025
|
-
description: 'An authorization role with permissions',
|
|
1026
|
-
|
|
1027
|
-
properties: {
|
|
1028
|
-
id: { type: 'string', description: 'Unique role identifier' },
|
|
1029
|
-
name: { type: 'string', description: 'Display name' },
|
|
1030
|
-
description: { type: 'string', optional: true, description: 'Role description' },
|
|
1031
|
-
resourceType: { type: 'string', description: 'Resource type this role is scoped to' },
|
|
1032
|
-
level: { type: 'string', optional: true, description: 'Role level (owner, admin, editor, viewer, guest)' },
|
|
1033
|
-
},
|
|
1034
|
-
|
|
1035
|
-
relationships: {
|
|
1036
|
-
permissions: { type: 'Permission[]', description: 'Permissions granted by this role' },
|
|
1037
|
-
inherits: { type: 'Role[]', description: 'Parent roles inherited from' },
|
|
1038
|
-
assignments: { type: 'Assignment[]', backref: 'role', description: 'Assignments using this role' },
|
|
1039
|
-
},
|
|
1040
|
-
|
|
1041
|
-
actions: ['create', 'update', 'delete', 'assign', 'unassign'],
|
|
1042
|
-
events: ['created', 'updated', 'deleted', 'assigned', 'unassigned'],
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
/**
|
|
1046
|
-
* Assignment as a Noun
|
|
1047
|
-
*/
|
|
1048
|
-
export const AssignmentNoun: Noun = {
|
|
1049
|
-
singular: 'assignment',
|
|
1050
|
-
plural: 'assignments',
|
|
1051
|
-
description: 'A role assignment binding subject, role, and resource',
|
|
1052
|
-
|
|
1053
|
-
properties: {
|
|
1054
|
-
id: { type: 'string', description: 'Unique assignment identifier' },
|
|
1055
|
-
subjectType: { type: 'string', description: 'Subject type (user, group, service, agent)' },
|
|
1056
|
-
subjectId: { type: 'string', description: 'Subject identifier' },
|
|
1057
|
-
roleId: { type: 'string', description: 'Role identifier' },
|
|
1058
|
-
resourceType: { type: 'string', description: 'Resource type' },
|
|
1059
|
-
resourceId: { type: 'string', description: 'Resource identifier' },
|
|
1060
|
-
expiresAt: { type: 'datetime', optional: true, description: 'Expiration timestamp' },
|
|
1061
|
-
},
|
|
1062
|
-
|
|
1063
|
-
relationships: {
|
|
1064
|
-
role: { type: 'Role', backref: 'assignments', description: 'The assigned role' },
|
|
1065
|
-
},
|
|
1066
|
-
|
|
1067
|
-
actions: ['create', 'delete', 'extend', 'revoke'],
|
|
1068
|
-
events: ['created', 'deleted', 'extended', 'revoked', 'expired'],
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
/**
|
|
1072
|
-
* Permission as a Noun
|
|
1073
|
-
*/
|
|
1074
|
-
export const PermissionNoun: Noun = {
|
|
1075
|
-
singular: 'permission',
|
|
1076
|
-
plural: 'permissions',
|
|
1077
|
-
description: 'A permission granting actions on a resource type',
|
|
1078
|
-
|
|
1079
|
-
properties: {
|
|
1080
|
-
name: { type: 'string', description: 'Permission name' },
|
|
1081
|
-
description: { type: 'string', optional: true, description: 'Permission description' },
|
|
1082
|
-
resourceType: { type: 'string', description: 'Resource type this applies to' },
|
|
1083
|
-
actions: { type: 'string', array: true, description: 'Actions granted' },
|
|
1084
|
-
inheritable: { type: 'boolean', optional: true, description: 'Whether permission flows to children' },
|
|
1085
|
-
},
|
|
1086
|
-
|
|
1087
|
-
relationships: {
|
|
1088
|
-
roles: { type: 'Role[]', backref: 'permissions', description: 'Roles that include this permission' },
|
|
1089
|
-
},
|
|
1090
|
-
|
|
1091
|
-
actions: ['create', 'update', 'delete'],
|
|
1092
|
-
events: ['created', 'updated', 'deleted'],
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
/**
|
|
1096
|
-
* All authorization-related Nouns
|
|
1097
|
-
*/
|
|
1098
|
-
export const AuthorizationNouns = {
|
|
1099
|
-
Role: RoleNoun,
|
|
1100
|
-
Assignment: AssignmentNoun,
|
|
1101
|
-
Permission: PermissionNoun,
|
|
1102
|
-
}
|