@kysera/rls 0.5.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/LICENSE +21 -0
- package/README.md +1341 -0
- package/dist/index.d.ts +705 -0
- package/dist/index.js +1471 -0
- package/dist/index.js.map +1 -0
- package/dist/native/index.d.ts +91 -0
- package/dist/native/index.js +253 -0
- package/dist/native/index.js.map +1 -0
- package/dist/types-Dtg6Lt1k.d.ts +633 -0
- package/package.json +93 -0
- package/src/context/index.ts +9 -0
- package/src/context/manager.ts +203 -0
- package/src/context/storage.ts +8 -0
- package/src/context/types.ts +5 -0
- package/src/errors.ts +280 -0
- package/src/index.ts +95 -0
- package/src/native/README.md +315 -0
- package/src/native/index.ts +11 -0
- package/src/native/migration.ts +92 -0
- package/src/native/postgres.ts +263 -0
- package/src/plugin.ts +464 -0
- package/src/policy/builder.ts +215 -0
- package/src/policy/index.ts +10 -0
- package/src/policy/registry.ts +403 -0
- package/src/policy/schema.ts +257 -0
- package/src/policy/types.ts +742 -0
- package/src/transformer/index.ts +2 -0
- package/src/transformer/mutation.ts +372 -0
- package/src/transformer/select.ts +150 -0
- package/src/utils/helpers.ts +139 -0
- package/src/utils/index.ts +12 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for RLS (Row-Level Security) policies
|
|
3
|
+
*
|
|
4
|
+
* This module provides comprehensive type definitions for defining and evaluating
|
|
5
|
+
* row-level security policies in Kysera ORM. It supports multiple policy types,
|
|
6
|
+
* flexible context management, and type-safe policy definitions.
|
|
7
|
+
*
|
|
8
|
+
* @module @kysera/rls/policy/types
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Kysely } from 'kysely';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Operation Types
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Database operations that can be controlled by RLS policies
|
|
19
|
+
*
|
|
20
|
+
* - `read`: SELECT operations
|
|
21
|
+
* - `create`: INSERT operations
|
|
22
|
+
* - `update`: UPDATE operations
|
|
23
|
+
* - `delete`: DELETE operations
|
|
24
|
+
* - `all`: All operations (wildcard)
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const policy: PolicyDefinition = {
|
|
29
|
+
* type: 'allow',
|
|
30
|
+
* operation: ['read', 'update'], // Multiple operations
|
|
31
|
+
* condition: (ctx) => ctx.auth.userId === ctx.row.userId
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export type Operation = 'read' | 'create' | 'update' | 'delete' | 'all';
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Authentication Context
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Authentication context containing user identity and authorization information
|
|
43
|
+
*
|
|
44
|
+
* This context is passed to all policy evaluation functions and contains
|
|
45
|
+
* information about the authenticated user, their roles, and permissions.
|
|
46
|
+
*
|
|
47
|
+
* @typeParam TUser - Custom user type for additional user properties
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const authContext: RLSAuthContext = {
|
|
52
|
+
* userId: 123,
|
|
53
|
+
* roles: ['user', 'editor'],
|
|
54
|
+
* tenantId: 'acme-corp',
|
|
55
|
+
* organizationIds: ['org-1', 'org-2'],
|
|
56
|
+
* permissions: ['posts:read', 'posts:write'],
|
|
57
|
+
* isSystem: false
|
|
58
|
+
* };
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export interface RLSAuthContext<TUser = unknown> {
|
|
62
|
+
/**
|
|
63
|
+
* Unique identifier for the authenticated user
|
|
64
|
+
* Can be a string or number depending on your ID strategy
|
|
65
|
+
*/
|
|
66
|
+
userId: string | number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* List of roles assigned to the user
|
|
70
|
+
* Used for role-based access control (RBAC)
|
|
71
|
+
*/
|
|
72
|
+
roles: string[];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Optional tenant identifier for multi-tenancy
|
|
76
|
+
* Use for tenant isolation in SaaS applications
|
|
77
|
+
*/
|
|
78
|
+
tenantId?: string | number;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Optional list of organization IDs the user belongs to
|
|
82
|
+
* Use for hierarchical multi-tenancy or organization-based access
|
|
83
|
+
*/
|
|
84
|
+
organizationIds?: (string | number)[];
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Optional list of granular permissions
|
|
88
|
+
* Use for fine-grained access control
|
|
89
|
+
*/
|
|
90
|
+
permissions?: string[];
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Optional custom attributes for advanced policy logic
|
|
94
|
+
* Can contain any additional context needed for policy evaluation
|
|
95
|
+
*/
|
|
96
|
+
attributes?: Record<string, unknown>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Optional full user object for accessing user properties
|
|
100
|
+
* Useful when policies need to check user-specific attributes
|
|
101
|
+
*/
|
|
102
|
+
user?: TUser;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Flag indicating if this is a system/admin context
|
|
106
|
+
* System contexts typically bypass RLS policies
|
|
107
|
+
*
|
|
108
|
+
* @default false
|
|
109
|
+
*/
|
|
110
|
+
isSystem?: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Request Context
|
|
115
|
+
// ============================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* HTTP request context for audit and policy evaluation
|
|
119
|
+
*
|
|
120
|
+
* Contains information about the current request, useful for logging,
|
|
121
|
+
* audit trails, and IP-based or time-based access policies.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const requestContext: RLSRequestContext = {
|
|
126
|
+
* requestId: 'req-123abc',
|
|
127
|
+
* ipAddress: '192.168.1.100',
|
|
128
|
+
* userAgent: 'Mozilla/5.0...',
|
|
129
|
+
* timestamp: new Date(),
|
|
130
|
+
* headers: { 'x-api-key': 'secret' }
|
|
131
|
+
* };
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export interface RLSRequestContext {
|
|
135
|
+
/**
|
|
136
|
+
* Unique identifier for the request
|
|
137
|
+
* Useful for tracing and debugging
|
|
138
|
+
*/
|
|
139
|
+
requestId?: string;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Client IP address
|
|
143
|
+
* Can be used for IP-based access policies
|
|
144
|
+
*/
|
|
145
|
+
ipAddress?: string;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Client user agent string
|
|
149
|
+
* Useful for device-based access policies
|
|
150
|
+
*/
|
|
151
|
+
userAgent?: string;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Request timestamp
|
|
155
|
+
* Required for time-based policies and audit logs
|
|
156
|
+
*/
|
|
157
|
+
timestamp: Date;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* HTTP headers
|
|
161
|
+
* Can contain custom authentication or context headers
|
|
162
|
+
*/
|
|
163
|
+
headers?: Record<string, string>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Complete RLS Context
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Complete RLS context containing all information for policy evaluation
|
|
172
|
+
*
|
|
173
|
+
* This is the main context object passed to policy functions and used
|
|
174
|
+
* throughout the RLS system.
|
|
175
|
+
*
|
|
176
|
+
* @typeParam TUser - Custom user type
|
|
177
|
+
* @typeParam TMeta - Custom metadata type for additional context
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const rlsContext: RLSContext = {
|
|
182
|
+
* auth: {
|
|
183
|
+
* userId: 123,
|
|
184
|
+
* roles: ['user'],
|
|
185
|
+
* tenantId: 'acme-corp'
|
|
186
|
+
* },
|
|
187
|
+
* request: {
|
|
188
|
+
* requestId: 'req-123',
|
|
189
|
+
* ipAddress: '192.168.1.1',
|
|
190
|
+
* timestamp: new Date()
|
|
191
|
+
* },
|
|
192
|
+
* meta: {
|
|
193
|
+
* feature_flags: ['new_ui', 'beta_access']
|
|
194
|
+
* },
|
|
195
|
+
* timestamp: new Date()
|
|
196
|
+
* };
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export interface RLSContext<TUser = unknown, TMeta = unknown> {
|
|
200
|
+
/**
|
|
201
|
+
* Authentication context (required)
|
|
202
|
+
* Contains user identity and authorization information
|
|
203
|
+
*/
|
|
204
|
+
auth: RLSAuthContext<TUser>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Request context (optional)
|
|
208
|
+
* Contains HTTP request information
|
|
209
|
+
*/
|
|
210
|
+
request?: RLSRequestContext;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Custom metadata (optional)
|
|
214
|
+
* Can contain any additional context needed for policy evaluation
|
|
215
|
+
* Examples: feature flags, A/B test groups, regional settings
|
|
216
|
+
*/
|
|
217
|
+
meta?: TMeta;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Context creation timestamp
|
|
221
|
+
* Used for temporal policies and audit trails
|
|
222
|
+
*/
|
|
223
|
+
timestamp: Date;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Policy Evaluation Context
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Context passed to policy evaluation functions
|
|
232
|
+
*
|
|
233
|
+
* This extended context includes the authentication/request context plus
|
|
234
|
+
* additional information about the current row being evaluated and the
|
|
235
|
+
* data being operated on.
|
|
236
|
+
*
|
|
237
|
+
* @typeParam TAuth - Custom user type for auth context
|
|
238
|
+
* @typeParam TRow - Type of the database row being evaluated
|
|
239
|
+
* @typeParam TData - Type of the data being inserted/updated
|
|
240
|
+
* @typeParam TDB - Database schema type for Kysely
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* // Policy function using evaluation context
|
|
245
|
+
* const ownershipPolicy = (ctx: PolicyEvaluationContext<User, Post>) => {
|
|
246
|
+
* // Check if user owns the post
|
|
247
|
+
* return ctx.auth.userId === ctx.row.authorId;
|
|
248
|
+
* };
|
|
249
|
+
*
|
|
250
|
+
* // Filter policy using evaluation context
|
|
251
|
+
* const tenantFilter = (ctx: PolicyEvaluationContext) => {
|
|
252
|
+
* return {
|
|
253
|
+
* tenant_id: ctx.auth.tenantId
|
|
254
|
+
* };
|
|
255
|
+
* };
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export interface PolicyEvaluationContext<
|
|
259
|
+
TAuth = unknown,
|
|
260
|
+
TRow = unknown,
|
|
261
|
+
TData = unknown,
|
|
262
|
+
TDB = unknown
|
|
263
|
+
> {
|
|
264
|
+
/**
|
|
265
|
+
* Authentication context
|
|
266
|
+
* Contains user identity and authorization information
|
|
267
|
+
*/
|
|
268
|
+
auth: RLSAuthContext<TAuth>;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Current row being evaluated (optional)
|
|
272
|
+
* Available during read/update/delete operations
|
|
273
|
+
* Used for row-level policies that check row attributes
|
|
274
|
+
*/
|
|
275
|
+
row?: TRow;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Data being inserted or updated (optional)
|
|
279
|
+
* Available during create/update operations
|
|
280
|
+
* Used for validation policies
|
|
281
|
+
*/
|
|
282
|
+
data?: TData;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Request context (optional)
|
|
286
|
+
* Contains HTTP request information
|
|
287
|
+
*/
|
|
288
|
+
request?: RLSRequestContext;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Kysely database instance (optional)
|
|
292
|
+
* Available for policies that need to perform additional queries
|
|
293
|
+
* Use sparingly as it can impact performance
|
|
294
|
+
*/
|
|
295
|
+
db?: Kysely<TDB>;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Custom metadata (optional)
|
|
299
|
+
* Can contain any additional context needed for policy evaluation
|
|
300
|
+
*/
|
|
301
|
+
meta?: Record<string, unknown>;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ============================================================================
|
|
305
|
+
// Policy Condition Types
|
|
306
|
+
// ============================================================================
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Policy condition function or expression
|
|
310
|
+
*
|
|
311
|
+
* Can be either:
|
|
312
|
+
* - A function that returns a boolean (or Promise<boolean>)
|
|
313
|
+
* - A string expression for native RLS (PostgreSQL)
|
|
314
|
+
*
|
|
315
|
+
* @typeParam TCtx - Policy evaluation context type
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* // Function-based condition
|
|
320
|
+
* const condition: PolicyCondition = (ctx) => {
|
|
321
|
+
* return ctx.auth.roles.includes('admin') ||
|
|
322
|
+
* ctx.auth.userId === ctx.row.ownerId;
|
|
323
|
+
* };
|
|
324
|
+
*
|
|
325
|
+
* // Async condition
|
|
326
|
+
* const asyncCondition: PolicyCondition = async (ctx) => {
|
|
327
|
+
* const hasPermission = await checkPermission(ctx.auth.userId, 'posts:read');
|
|
328
|
+
* return hasPermission;
|
|
329
|
+
* };
|
|
330
|
+
*
|
|
331
|
+
* // String expression for native RLS
|
|
332
|
+
* const nativeCondition: PolicyCondition = 'user_id = current_user_id()';
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
export type PolicyCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> =
|
|
336
|
+
| ((ctx: TCtx) => boolean | Promise<boolean>)
|
|
337
|
+
| string;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Filter condition that returns WHERE clause conditions
|
|
341
|
+
*
|
|
342
|
+
* Used for filter-type policies that add WHERE conditions to queries.
|
|
343
|
+
* Can be either:
|
|
344
|
+
* - A function that returns an object with column-value pairs
|
|
345
|
+
* - An object mapping column names to context property paths
|
|
346
|
+
*
|
|
347
|
+
* @typeParam TCtx - Policy evaluation context type
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* // Function-based filter
|
|
352
|
+
* const filter: FilterCondition = (ctx) => ({
|
|
353
|
+
* tenant_id: ctx.auth.tenantId,
|
|
354
|
+
* deleted_at: null
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* // Static filter mapping
|
|
358
|
+
* const staticFilter: FilterCondition = {
|
|
359
|
+
* tenant_id: 'auth.tenantId',
|
|
360
|
+
* status: 'meta.defaultStatus'
|
|
361
|
+
* };
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
export type FilterCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> =
|
|
365
|
+
| ((ctx: TCtx) => Record<string, unknown>)
|
|
366
|
+
| Record<string, string>;
|
|
367
|
+
|
|
368
|
+
// ============================================================================
|
|
369
|
+
// Policy Types
|
|
370
|
+
// ============================================================================
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Policy behavior type
|
|
374
|
+
*
|
|
375
|
+
* - `allow`: Grants access if condition is true
|
|
376
|
+
* - `deny`: Denies access if condition is true (takes precedence)
|
|
377
|
+
* - `filter`: Adds WHERE conditions to automatically filter rows
|
|
378
|
+
* - `validate`: Validates data during create/update operations
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* // Allow policy: grants access to owners
|
|
383
|
+
* const allowPolicy: PolicyDefinition = {
|
|
384
|
+
* type: 'allow',
|
|
385
|
+
* operation: 'update',
|
|
386
|
+
* condition: (ctx) => ctx.auth.userId === ctx.row.ownerId
|
|
387
|
+
* };
|
|
388
|
+
*
|
|
389
|
+
* // Deny policy: prevents access to deleted items
|
|
390
|
+
* const denyPolicy: PolicyDefinition = {
|
|
391
|
+
* type: 'deny',
|
|
392
|
+
* operation: 'read',
|
|
393
|
+
* condition: (ctx) => ctx.row.deletedAt !== null
|
|
394
|
+
* };
|
|
395
|
+
*
|
|
396
|
+
* // Filter policy: automatically filters by tenant
|
|
397
|
+
* const filterPolicy: PolicyDefinition = {
|
|
398
|
+
* type: 'filter',
|
|
399
|
+
* operation: 'read',
|
|
400
|
+
* condition: (ctx) => ({ tenant_id: ctx.auth.tenantId })
|
|
401
|
+
* };
|
|
402
|
+
*
|
|
403
|
+
* // Validate policy: ensures valid status transitions
|
|
404
|
+
* const validatePolicy: PolicyDefinition = {
|
|
405
|
+
* type: 'validate',
|
|
406
|
+
* operation: 'update',
|
|
407
|
+
* condition: (ctx) => isValidStatusTransition(ctx.row.status, ctx.data.status)
|
|
408
|
+
* };
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
export type PolicyType = 'allow' | 'deny' | 'filter' | 'validate';
|
|
412
|
+
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// Policy Definition
|
|
415
|
+
// ============================================================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Policy definition for RLS enforcement
|
|
419
|
+
*
|
|
420
|
+
* Defines a single security policy with its behavior, operations, and conditions.
|
|
421
|
+
*
|
|
422
|
+
* @typeParam TOperation - Operation type(s) the policy applies to
|
|
423
|
+
* @typeParam TCondition - Condition type (function or string)
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```typescript
|
|
427
|
+
* // Basic ownership policy
|
|
428
|
+
* const ownershipPolicy: PolicyDefinition = {
|
|
429
|
+
* type: 'allow',
|
|
430
|
+
* operation: ['read', 'update', 'delete'],
|
|
431
|
+
* condition: (ctx) => ctx.auth.userId === ctx.row.ownerId,
|
|
432
|
+
* name: 'ownership_policy',
|
|
433
|
+
* priority: 100
|
|
434
|
+
* };
|
|
435
|
+
*
|
|
436
|
+
* // Native PostgreSQL RLS policy
|
|
437
|
+
* const nativePolicy: PolicyDefinition = {
|
|
438
|
+
* type: 'allow',
|
|
439
|
+
* operation: 'read',
|
|
440
|
+
* condition: '',
|
|
441
|
+
* using: 'user_id = current_user_id()',
|
|
442
|
+
* withCheck: 'user_id = current_user_id()',
|
|
443
|
+
* role: 'authenticated',
|
|
444
|
+
* name: 'user_isolation'
|
|
445
|
+
* };
|
|
446
|
+
*
|
|
447
|
+
* // Multi-tenancy filter
|
|
448
|
+
* const tenantFilter: PolicyDefinition = {
|
|
449
|
+
* type: 'filter',
|
|
450
|
+
* operation: 'read',
|
|
451
|
+
* condition: (ctx) => ({ tenant_id: ctx.auth.tenantId }),
|
|
452
|
+
* name: 'tenant_isolation',
|
|
453
|
+
* priority: 1000 // High priority for tenant isolation
|
|
454
|
+
* };
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
export interface PolicyDefinition<
|
|
458
|
+
TOperation extends Operation = Operation,
|
|
459
|
+
TCondition = PolicyCondition
|
|
460
|
+
> {
|
|
461
|
+
/**
|
|
462
|
+
* Policy behavior type
|
|
463
|
+
* Determines how the policy affects access control
|
|
464
|
+
*/
|
|
465
|
+
type: PolicyType;
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Operation(s) this policy applies to
|
|
469
|
+
* Can be a single operation or an array of operations
|
|
470
|
+
*/
|
|
471
|
+
operation: TOperation | TOperation[];
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Condition function or expression
|
|
475
|
+
* - For 'allow'/'deny'/'validate': returns boolean
|
|
476
|
+
* - For 'filter': returns WHERE conditions object
|
|
477
|
+
* - For native RLS: SQL expression string
|
|
478
|
+
*/
|
|
479
|
+
condition: TCondition;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Optional policy name for debugging and logging
|
|
483
|
+
* Recommended for easier policy management
|
|
484
|
+
*/
|
|
485
|
+
name?: string;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Optional priority for policy evaluation order
|
|
489
|
+
* Higher priority policies are evaluated first
|
|
490
|
+
* Deny policies typically have higher priority
|
|
491
|
+
*
|
|
492
|
+
* @default 0
|
|
493
|
+
*/
|
|
494
|
+
priority?: number;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Optional SQL expression for native RLS USING clause
|
|
498
|
+
* PostgreSQL only - used for row visibility checks
|
|
499
|
+
*
|
|
500
|
+
* @example 'user_id = current_user_id()'
|
|
501
|
+
*/
|
|
502
|
+
using?: string;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Optional SQL expression for native RLS WITH CHECK clause
|
|
506
|
+
* PostgreSQL only - used for insert/update validation
|
|
507
|
+
*
|
|
508
|
+
* @example 'tenant_id = current_tenant_id()'
|
|
509
|
+
*/
|
|
510
|
+
withCheck?: string;
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Optional database role this policy applies to
|
|
514
|
+
* PostgreSQL only - restricts policy to specific roles
|
|
515
|
+
*
|
|
516
|
+
* @example 'authenticated'
|
|
517
|
+
*/
|
|
518
|
+
role?: string;
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Optional performance optimization hints
|
|
522
|
+
* Used for query optimization and index suggestions
|
|
523
|
+
*/
|
|
524
|
+
hints?: PolicyHints;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ============================================================================
|
|
528
|
+
// Table Configuration
|
|
529
|
+
// ============================================================================
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* RLS configuration for a single database table
|
|
533
|
+
*
|
|
534
|
+
* Defines all policies and settings for row-level security on a table.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```typescript
|
|
538
|
+
* const postsConfig: TableRLSConfig = {
|
|
539
|
+
* policies: [
|
|
540
|
+
* {
|
|
541
|
+
* type: 'filter',
|
|
542
|
+
* operation: 'read',
|
|
543
|
+
* condition: (ctx) => ({ tenant_id: ctx.auth.tenantId }),
|
|
544
|
+
* name: 'tenant_isolation',
|
|
545
|
+
* priority: 1000
|
|
546
|
+
* },
|
|
547
|
+
* {
|
|
548
|
+
* type: 'allow',
|
|
549
|
+
* operation: ['update', 'delete'],
|
|
550
|
+
* condition: (ctx) => ctx.auth.userId === ctx.row.authorId,
|
|
551
|
+
* name: 'author_access'
|
|
552
|
+
* }
|
|
553
|
+
* ],
|
|
554
|
+
* defaultDeny: true,
|
|
555
|
+
* skipFor: ['system', 'admin']
|
|
556
|
+
* };
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
export interface TableRLSConfig {
|
|
560
|
+
/**
|
|
561
|
+
* List of policies to enforce on this table
|
|
562
|
+
* Policies are evaluated in priority order (highest first)
|
|
563
|
+
*/
|
|
564
|
+
policies: PolicyDefinition[];
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Default behavior when no policies match
|
|
568
|
+
* - true: Deny access by default (secure default)
|
|
569
|
+
* - false: Allow access by default (open default)
|
|
570
|
+
*
|
|
571
|
+
* @default true
|
|
572
|
+
*/
|
|
573
|
+
defaultDeny?: boolean;
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* List of roles that bypass RLS policies
|
|
577
|
+
* Useful for system operations or admin accounts
|
|
578
|
+
*
|
|
579
|
+
* @example ['system', 'admin', 'superuser']
|
|
580
|
+
*/
|
|
581
|
+
skipFor?: string[];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ============================================================================
|
|
585
|
+
// Complete Schema
|
|
586
|
+
// ============================================================================
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Complete RLS schema for all tables in the database
|
|
590
|
+
*
|
|
591
|
+
* Maps table names to their RLS configurations.
|
|
592
|
+
*
|
|
593
|
+
* @typeParam DB - Database schema type (Kysely DB type)
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```typescript
|
|
597
|
+
* interface Database {
|
|
598
|
+
* posts: Post;
|
|
599
|
+
* comments: Comment;
|
|
600
|
+
* users: User;
|
|
601
|
+
* }
|
|
602
|
+
*
|
|
603
|
+
* const rlsSchema: RLSSchema<Database> = {
|
|
604
|
+
* posts: {
|
|
605
|
+
* policies: [
|
|
606
|
+
* {
|
|
607
|
+
* type: 'filter',
|
|
608
|
+
* operation: 'read',
|
|
609
|
+
* condition: (ctx) => ({ tenant_id: ctx.auth.tenantId })
|
|
610
|
+
* }
|
|
611
|
+
* ],
|
|
612
|
+
* defaultDeny: true
|
|
613
|
+
* },
|
|
614
|
+
* comments: {
|
|
615
|
+
* policies: [
|
|
616
|
+
* {
|
|
617
|
+
* type: 'allow',
|
|
618
|
+
* operation: 'all',
|
|
619
|
+
* condition: (ctx) => ctx.auth.roles.includes('moderator')
|
|
620
|
+
* }
|
|
621
|
+
* ]
|
|
622
|
+
* }
|
|
623
|
+
* };
|
|
624
|
+
* ```
|
|
625
|
+
*/
|
|
626
|
+
export type RLSSchema<DB> = {
|
|
627
|
+
[K in keyof DB]?: TableRLSConfig;
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
// ============================================================================
|
|
631
|
+
// Compiled Policies
|
|
632
|
+
// ============================================================================
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Compiled policy ready for runtime evaluation
|
|
636
|
+
*
|
|
637
|
+
* Internal representation of a policy after compilation and optimization.
|
|
638
|
+
*
|
|
639
|
+
* @typeParam TCtx - Policy evaluation context type
|
|
640
|
+
*/
|
|
641
|
+
export interface CompiledPolicy<TCtx = PolicyEvaluationContext> {
|
|
642
|
+
/**
|
|
643
|
+
* Policy behavior type
|
|
644
|
+
*/
|
|
645
|
+
type: PolicyType;
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Operations this policy applies to
|
|
649
|
+
* Always an array after compilation
|
|
650
|
+
*/
|
|
651
|
+
operation: Operation[];
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Compiled evaluation function
|
|
655
|
+
* Returns boolean or Promise<boolean>
|
|
656
|
+
*/
|
|
657
|
+
evaluate: (ctx: TCtx) => boolean | Promise<boolean>;
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Policy name for debugging
|
|
661
|
+
*/
|
|
662
|
+
name: string;
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Priority for evaluation order
|
|
666
|
+
*/
|
|
667
|
+
priority: number;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Compiled filter policy for query transformation
|
|
672
|
+
*
|
|
673
|
+
* Specialized compiled policy type for filter operations.
|
|
674
|
+
*
|
|
675
|
+
* @typeParam TCtx - Policy evaluation context type
|
|
676
|
+
*/
|
|
677
|
+
export interface CompiledFilterPolicy<TCtx = PolicyEvaluationContext> {
|
|
678
|
+
/**
|
|
679
|
+
* Always 'read' for filter policies
|
|
680
|
+
*/
|
|
681
|
+
operation: 'read';
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Function to get WHERE conditions
|
|
685
|
+
* Returns object with column-value pairs
|
|
686
|
+
*/
|
|
687
|
+
getConditions: (ctx: TCtx) => Record<string, unknown>;
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Policy name for debugging
|
|
691
|
+
*/
|
|
692
|
+
name: string;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ============================================================================
|
|
696
|
+
// Policy Optimization Hints
|
|
697
|
+
// ============================================================================
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Optimization hints for policy execution
|
|
701
|
+
*
|
|
702
|
+
* Provides metadata to help optimize policy evaluation and query generation.
|
|
703
|
+
* These hints can be used by query optimizers and execution planners.
|
|
704
|
+
*
|
|
705
|
+
* @example
|
|
706
|
+
* ```typescript
|
|
707
|
+
* const hints: PolicyHints = {
|
|
708
|
+
* indexColumns: ['tenant_id', 'user_id'],
|
|
709
|
+
* selectivity: 'high',
|
|
710
|
+
* leakproof: true,
|
|
711
|
+
* stable: true
|
|
712
|
+
* };
|
|
713
|
+
* ```
|
|
714
|
+
*/
|
|
715
|
+
export interface PolicyHints {
|
|
716
|
+
/**
|
|
717
|
+
* Columns that should be indexed for optimal performance
|
|
718
|
+
* Suggests which columns are frequently used in policy conditions
|
|
719
|
+
*/
|
|
720
|
+
indexColumns?: string[];
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Expected selectivity of the policy
|
|
724
|
+
* - 'high': Filters out most rows (good for early evaluation)
|
|
725
|
+
* - 'medium': Filters moderate number of rows
|
|
726
|
+
* - 'low': Filters few rows (evaluate later)
|
|
727
|
+
*/
|
|
728
|
+
selectivity?: 'high' | 'medium' | 'low';
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Whether the policy is leakproof (PostgreSQL concept)
|
|
732
|
+
* Leakproof functions don't reveal information about their inputs
|
|
733
|
+
* Safe to execute before other security checks
|
|
734
|
+
*/
|
|
735
|
+
leakproof?: boolean;
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Whether the policy result is stable for the same inputs
|
|
739
|
+
* Stable policies can be cached during a query execution
|
|
740
|
+
*/
|
|
741
|
+
stable?: boolean;
|
|
742
|
+
}
|