@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.
@@ -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
+ }