@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 @@
1
+ {"version":3,"sources":["../../src/native/postgres.ts","../../src/native/migration.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Kysely } from 'kysely';\nimport { sql } from 'kysely';\nimport type { RLSSchema, TableRLSConfig, PolicyDefinition, Operation } from '../policy/types.js';\n\n/**\n * Options for PostgreSQL RLS generation\n */\nexport interface PostgresRLSOptions {\n /** Force RLS on table owners */\n force?: boolean;\n /** Schema name (default: public) */\n schemaName?: string;\n /** Prefix for generated policy names */\n policyPrefix?: string;\n}\n\n/**\n * PostgreSQL RLS Generator\n * Generates native PostgreSQL RLS statements from Kysera RLS schema\n */\nexport class PostgresRLSGenerator {\n /**\n * Generate all PostgreSQL RLS statements from schema\n */\n generateStatements<DB>(\n schema: RLSSchema<DB>,\n options: PostgresRLSOptions = {}\n ): string[] {\n const {\n force = true,\n schemaName = 'public',\n policyPrefix = 'rls',\n } = options;\n\n const statements: string[] = [];\n\n for (const [table, config] of Object.entries(schema)) {\n if (!config) continue;\n\n const qualifiedTable = `${schemaName}.${table}`;\n const tableConfig = config as TableRLSConfig;\n\n // Enable RLS on table\n statements.push(`ALTER TABLE ${qualifiedTable} ENABLE ROW LEVEL SECURITY;`);\n\n if (force) {\n statements.push(`ALTER TABLE ${qualifiedTable} FORCE ROW LEVEL SECURITY;`);\n }\n\n // Generate policies\n let policyIndex = 0;\n for (const policy of tableConfig.policies) {\n const policyName = policy.name ?? `${policyPrefix}_${table}_${policy.type}_${policyIndex++}`;\n const policySQL = this.generatePolicy(qualifiedTable, policyName, policy);\n if (policySQL) {\n statements.push(policySQL);\n }\n }\n }\n\n return statements;\n }\n\n /**\n * Generate a single policy statement\n */\n private generatePolicy(\n table: string,\n name: string,\n policy: PolicyDefinition\n ): string | null {\n // Skip filter policies (they're ORM-only)\n if (policy.type === 'filter' || policy.type === 'validate') {\n return null;\n }\n\n // Need USING or WITH CHECK clause for native RLS\n if (!policy.using && !policy.withCheck) {\n return null;\n }\n\n const parts: string[] = [\n `CREATE POLICY \"${name}\"`,\n `ON ${table}`,\n ];\n\n // Policy type\n if (policy.type === 'deny') {\n parts.push('AS RESTRICTIVE');\n } else {\n parts.push('AS PERMISSIVE');\n }\n\n // Target role\n parts.push(`TO ${policy.role ?? 'public'}`);\n\n // Operation\n parts.push(`FOR ${this.mapOperation(policy.operation)}`);\n\n // USING clause\n if (policy.using) {\n parts.push(`USING (${policy.using})`);\n }\n\n // WITH CHECK clause\n if (policy.withCheck) {\n parts.push(`WITH CHECK (${policy.withCheck})`);\n }\n\n return parts.join('\\n ') + ';';\n }\n\n /**\n * Map Kysera operation to PostgreSQL operation\n */\n private mapOperation(operation: Operation | Operation[]): string {\n if (Array.isArray(operation)) {\n if (operation.length === 0) {\n return 'ALL';\n }\n if (operation.length === 4 || operation.includes('all')) {\n return 'ALL';\n }\n // PostgreSQL doesn't support multiple operations in one policy\n // Return first operation\n return this.mapSingleOperation(operation[0]!);\n }\n return this.mapSingleOperation(operation);\n }\n\n /**\n * Map single operation\n */\n private mapSingleOperation(op: Operation): string {\n switch (op) {\n case 'read': return 'SELECT';\n case 'create': return 'INSERT';\n case 'update': return 'UPDATE';\n case 'delete': return 'DELETE';\n case 'all': return 'ALL';\n default: return 'ALL';\n }\n }\n\n /**\n * Generate context-setting functions for PostgreSQL\n * These functions should be STABLE for optimal performance\n */\n generateContextFunctions(): string {\n return `\n-- RLS Context Functions (STABLE for query planner optimization)\n-- These functions read session variables set by the application\n\nCREATE OR REPLACE FUNCTION rls_current_user_id()\nRETURNS text\nLANGUAGE SQL STABLE\nAS $$ SELECT current_setting('app.user_id', true) $$;\n\nCREATE OR REPLACE FUNCTION rls_current_tenant_id()\nRETURNS uuid\nLANGUAGE SQL STABLE\nAS $$ SELECT NULLIF(current_setting('app.tenant_id', true), '')::uuid $$;\n\nCREATE OR REPLACE FUNCTION rls_current_roles()\nRETURNS text[]\nLANGUAGE SQL STABLE\nAS $$ SELECT string_to_array(COALESCE(current_setting('app.roles', true), ''), ',') $$;\n\nCREATE OR REPLACE FUNCTION rls_has_role(role_name text)\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT role_name = ANY(rls_current_roles()) $$;\n\nCREATE OR REPLACE FUNCTION rls_current_permissions()\nRETURNS text[]\nLANGUAGE SQL STABLE\nAS $$ SELECT string_to_array(COALESCE(current_setting('app.permissions', true), ''), ',') $$;\n\nCREATE OR REPLACE FUNCTION rls_has_permission(permission_name text)\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT permission_name = ANY(rls_current_permissions()) $$;\n\nCREATE OR REPLACE FUNCTION rls_is_system()\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT COALESCE(current_setting('app.is_system', true), 'false')::boolean $$;\n`;\n }\n\n /**\n * Generate DROP statements for cleaning up\n */\n generateDropStatements<DB>(\n schema: RLSSchema<DB>,\n options: PostgresRLSOptions = {}\n ): string[] {\n const { schemaName = 'public', policyPrefix = 'rls' } = options;\n const statements: string[] = [];\n\n for (const table of Object.keys(schema)) {\n const qualifiedTable = `${schemaName}.${table}`;\n\n // Drop all policies with prefix\n statements.push(\n `DO $$ BEGIN\n EXECUTE (\n SELECT string_agg('DROP POLICY IF EXISTS ' || quote_ident(policyname) || ' ON ${qualifiedTable};', E'\\\\n')\n FROM pg_policies\n WHERE tablename = '${table}'\n AND schemaname = '${schemaName}'\n AND policyname LIKE '${policyPrefix}_%'\n );\nEND $$;`\n );\n\n // Disable RLS\n statements.push(`ALTER TABLE ${qualifiedTable} DISABLE ROW LEVEL SECURITY;`);\n }\n\n return statements;\n }\n}\n\n/**\n * Sync RLS context to PostgreSQL session settings\n * Call this at the start of each request/transaction\n */\nexport async function syncContextToPostgres<DB>(\n db: Kysely<DB>,\n context: {\n userId: string | number;\n tenantId?: string | number;\n roles?: string[];\n permissions?: string[];\n isSystem?: boolean;\n }\n): Promise<void> {\n const { userId, tenantId, roles, permissions, isSystem } = context;\n\n await sql`\n SELECT\n set_config('app.user_id', ${String(userId)}, true),\n set_config('app.tenant_id', ${tenantId ? String(tenantId) : ''}, true),\n set_config('app.roles', ${(roles ?? []).join(',')}, true),\n set_config('app.permissions', ${(permissions ?? []).join(',')}, true),\n set_config('app.is_system', ${isSystem ? 'true' : 'false'}, true)\n `.execute(db);\n}\n\n/**\n * Clear RLS context from PostgreSQL session\n */\nexport async function clearPostgresContext<DB>(db: Kysely<DB>): Promise<void> {\n await sql`\n SELECT\n set_config('app.user_id', '', true),\n set_config('app.tenant_id', '', true),\n set_config('app.roles', '', true),\n set_config('app.permissions', '', true),\n set_config('app.is_system', 'false', true)\n `.execute(db);\n}\n","import type { RLSSchema } from '../policy/types.js';\nimport { PostgresRLSGenerator, type PostgresRLSOptions } from './postgres.js';\n\n/**\n * Options for migration generation\n */\nexport interface MigrationOptions extends PostgresRLSOptions {\n /** Migration name */\n name?: string;\n /** Include context functions in migration */\n includeContextFunctions?: boolean;\n}\n\n/**\n * RLS Migration Generator\n * Generates Kysely migration files for RLS policies\n */\nexport class RLSMigrationGenerator {\n private generator = new PostgresRLSGenerator();\n\n /**\n * Generate migration file content\n */\n generateMigration<DB>(\n schema: RLSSchema<DB>,\n options: MigrationOptions = {}\n ): string {\n const {\n name = 'rls_policies',\n includeContextFunctions = true,\n ...generatorOptions\n } = options;\n\n const upStatements = this.generator.generateStatements(schema, generatorOptions);\n const downStatements = this.generator.generateDropStatements(schema, generatorOptions);\n\n const contextFunctions = includeContextFunctions\n ? this.generator.generateContextFunctions()\n : '';\n\n return `import { Kysely, sql } from 'kysely';\n\n/**\n * Migration: ${name}\n * Generated by @kysera/rls\n *\n * This migration sets up Row-Level Security policies for the database.\n */\n\nexport async function up(db: Kysely<any>): Promise<void> {\n${includeContextFunctions ? ` // Create RLS context functions\n await sql.raw(\\`${this.escapeTemplate(contextFunctions)}\\`).execute(db);\n\n` : ''} // Enable RLS and create policies\n${upStatements.map(s => ` await sql.raw(\\`${this.escapeTemplate(s)}\\`).execute(db);`).join('\\n')}\n}\n\nexport async function down(db: Kysely<any>): Promise<void> {\n${downStatements.map(s => ` await sql.raw(\\`${this.escapeTemplate(s)}\\`).execute(db);`).join('\\n')}\n${includeContextFunctions ? `\n // Drop RLS context functions\n await sql.raw(\\`\n DROP FUNCTION IF EXISTS rls_current_user_id();\n DROP FUNCTION IF EXISTS rls_current_tenant_id();\n DROP FUNCTION IF EXISTS rls_current_roles();\n DROP FUNCTION IF EXISTS rls_has_role(text);\n DROP FUNCTION IF EXISTS rls_current_permissions();\n DROP FUNCTION IF EXISTS rls_has_permission(text);\n DROP FUNCTION IF EXISTS rls_is_system();\n \\`).execute(db);` : ''}\n}\n`;\n }\n\n /**\n * Escape template literal for embedding in string\n */\n private escapeTemplate(str: string): string {\n return str.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n }\n\n /**\n * Generate migration filename with timestamp\n */\n generateFilename(name: string = 'rls_policies'): string {\n const timestamp = new Date().toISOString()\n .replace(/[-:]/g, '')\n .replace('T', '_')\n .replace(/\\..+/, '');\n return `${timestamp}_${name}.ts`;\n }\n}\n"]}
@@ -0,0 +1,633 @@
1
+ import { Kysely } from 'kysely';
2
+
3
+ /**
4
+ * Core type definitions for RLS (Row-Level Security) policies
5
+ *
6
+ * This module provides comprehensive type definitions for defining and evaluating
7
+ * row-level security policies in Kysera ORM. It supports multiple policy types,
8
+ * flexible context management, and type-safe policy definitions.
9
+ *
10
+ * @module @kysera/rls/policy/types
11
+ */
12
+
13
+ /**
14
+ * Database operations that can be controlled by RLS policies
15
+ *
16
+ * - `read`: SELECT operations
17
+ * - `create`: INSERT operations
18
+ * - `update`: UPDATE operations
19
+ * - `delete`: DELETE operations
20
+ * - `all`: All operations (wildcard)
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const policy: PolicyDefinition = {
25
+ * type: 'allow',
26
+ * operation: ['read', 'update'], // Multiple operations
27
+ * condition: (ctx) => ctx.auth.userId === ctx.row.userId
28
+ * };
29
+ * ```
30
+ */
31
+ type Operation = 'read' | 'create' | 'update' | 'delete' | 'all';
32
+ /**
33
+ * Authentication context containing user identity and authorization information
34
+ *
35
+ * This context is passed to all policy evaluation functions and contains
36
+ * information about the authenticated user, their roles, and permissions.
37
+ *
38
+ * @typeParam TUser - Custom user type for additional user properties
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const authContext: RLSAuthContext = {
43
+ * userId: 123,
44
+ * roles: ['user', 'editor'],
45
+ * tenantId: 'acme-corp',
46
+ * organizationIds: ['org-1', 'org-2'],
47
+ * permissions: ['posts:read', 'posts:write'],
48
+ * isSystem: false
49
+ * };
50
+ * ```
51
+ */
52
+ interface RLSAuthContext<TUser = unknown> {
53
+ /**
54
+ * Unique identifier for the authenticated user
55
+ * Can be a string or number depending on your ID strategy
56
+ */
57
+ userId: string | number;
58
+ /**
59
+ * List of roles assigned to the user
60
+ * Used for role-based access control (RBAC)
61
+ */
62
+ roles: string[];
63
+ /**
64
+ * Optional tenant identifier for multi-tenancy
65
+ * Use for tenant isolation in SaaS applications
66
+ */
67
+ tenantId?: string | number;
68
+ /**
69
+ * Optional list of organization IDs the user belongs to
70
+ * Use for hierarchical multi-tenancy or organization-based access
71
+ */
72
+ organizationIds?: (string | number)[];
73
+ /**
74
+ * Optional list of granular permissions
75
+ * Use for fine-grained access control
76
+ */
77
+ permissions?: string[];
78
+ /**
79
+ * Optional custom attributes for advanced policy logic
80
+ * Can contain any additional context needed for policy evaluation
81
+ */
82
+ attributes?: Record<string, unknown>;
83
+ /**
84
+ * Optional full user object for accessing user properties
85
+ * Useful when policies need to check user-specific attributes
86
+ */
87
+ user?: TUser;
88
+ /**
89
+ * Flag indicating if this is a system/admin context
90
+ * System contexts typically bypass RLS policies
91
+ *
92
+ * @default false
93
+ */
94
+ isSystem?: boolean;
95
+ }
96
+ /**
97
+ * HTTP request context for audit and policy evaluation
98
+ *
99
+ * Contains information about the current request, useful for logging,
100
+ * audit trails, and IP-based or time-based access policies.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const requestContext: RLSRequestContext = {
105
+ * requestId: 'req-123abc',
106
+ * ipAddress: '192.168.1.100',
107
+ * userAgent: 'Mozilla/5.0...',
108
+ * timestamp: new Date(),
109
+ * headers: { 'x-api-key': 'secret' }
110
+ * };
111
+ * ```
112
+ */
113
+ interface RLSRequestContext {
114
+ /**
115
+ * Unique identifier for the request
116
+ * Useful for tracing and debugging
117
+ */
118
+ requestId?: string;
119
+ /**
120
+ * Client IP address
121
+ * Can be used for IP-based access policies
122
+ */
123
+ ipAddress?: string;
124
+ /**
125
+ * Client user agent string
126
+ * Useful for device-based access policies
127
+ */
128
+ userAgent?: string;
129
+ /**
130
+ * Request timestamp
131
+ * Required for time-based policies and audit logs
132
+ */
133
+ timestamp: Date;
134
+ /**
135
+ * HTTP headers
136
+ * Can contain custom authentication or context headers
137
+ */
138
+ headers?: Record<string, string>;
139
+ }
140
+ /**
141
+ * Complete RLS context containing all information for policy evaluation
142
+ *
143
+ * This is the main context object passed to policy functions and used
144
+ * throughout the RLS system.
145
+ *
146
+ * @typeParam TUser - Custom user type
147
+ * @typeParam TMeta - Custom metadata type for additional context
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * const rlsContext: RLSContext = {
152
+ * auth: {
153
+ * userId: 123,
154
+ * roles: ['user'],
155
+ * tenantId: 'acme-corp'
156
+ * },
157
+ * request: {
158
+ * requestId: 'req-123',
159
+ * ipAddress: '192.168.1.1',
160
+ * timestamp: new Date()
161
+ * },
162
+ * meta: {
163
+ * feature_flags: ['new_ui', 'beta_access']
164
+ * },
165
+ * timestamp: new Date()
166
+ * };
167
+ * ```
168
+ */
169
+ interface RLSContext<TUser = unknown, TMeta = unknown> {
170
+ /**
171
+ * Authentication context (required)
172
+ * Contains user identity and authorization information
173
+ */
174
+ auth: RLSAuthContext<TUser>;
175
+ /**
176
+ * Request context (optional)
177
+ * Contains HTTP request information
178
+ */
179
+ request?: RLSRequestContext;
180
+ /**
181
+ * Custom metadata (optional)
182
+ * Can contain any additional context needed for policy evaluation
183
+ * Examples: feature flags, A/B test groups, regional settings
184
+ */
185
+ meta?: TMeta;
186
+ /**
187
+ * Context creation timestamp
188
+ * Used for temporal policies and audit trails
189
+ */
190
+ timestamp: Date;
191
+ }
192
+ /**
193
+ * Context passed to policy evaluation functions
194
+ *
195
+ * This extended context includes the authentication/request context plus
196
+ * additional information about the current row being evaluated and the
197
+ * data being operated on.
198
+ *
199
+ * @typeParam TAuth - Custom user type for auth context
200
+ * @typeParam TRow - Type of the database row being evaluated
201
+ * @typeParam TData - Type of the data being inserted/updated
202
+ * @typeParam TDB - Database schema type for Kysely
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * // Policy function using evaluation context
207
+ * const ownershipPolicy = (ctx: PolicyEvaluationContext<User, Post>) => {
208
+ * // Check if user owns the post
209
+ * return ctx.auth.userId === ctx.row.authorId;
210
+ * };
211
+ *
212
+ * // Filter policy using evaluation context
213
+ * const tenantFilter = (ctx: PolicyEvaluationContext) => {
214
+ * return {
215
+ * tenant_id: ctx.auth.tenantId
216
+ * };
217
+ * };
218
+ * ```
219
+ */
220
+ interface PolicyEvaluationContext<TAuth = unknown, TRow = unknown, TData = unknown, TDB = unknown> {
221
+ /**
222
+ * Authentication context
223
+ * Contains user identity and authorization information
224
+ */
225
+ auth: RLSAuthContext<TAuth>;
226
+ /**
227
+ * Current row being evaluated (optional)
228
+ * Available during read/update/delete operations
229
+ * Used for row-level policies that check row attributes
230
+ */
231
+ row?: TRow;
232
+ /**
233
+ * Data being inserted or updated (optional)
234
+ * Available during create/update operations
235
+ * Used for validation policies
236
+ */
237
+ data?: TData;
238
+ /**
239
+ * Request context (optional)
240
+ * Contains HTTP request information
241
+ */
242
+ request?: RLSRequestContext;
243
+ /**
244
+ * Kysely database instance (optional)
245
+ * Available for policies that need to perform additional queries
246
+ * Use sparingly as it can impact performance
247
+ */
248
+ db?: Kysely<TDB>;
249
+ /**
250
+ * Custom metadata (optional)
251
+ * Can contain any additional context needed for policy evaluation
252
+ */
253
+ meta?: Record<string, unknown>;
254
+ }
255
+ /**
256
+ * Policy condition function or expression
257
+ *
258
+ * Can be either:
259
+ * - A function that returns a boolean (or Promise<boolean>)
260
+ * - A string expression for native RLS (PostgreSQL)
261
+ *
262
+ * @typeParam TCtx - Policy evaluation context type
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * // Function-based condition
267
+ * const condition: PolicyCondition = (ctx) => {
268
+ * return ctx.auth.roles.includes('admin') ||
269
+ * ctx.auth.userId === ctx.row.ownerId;
270
+ * };
271
+ *
272
+ * // Async condition
273
+ * const asyncCondition: PolicyCondition = async (ctx) => {
274
+ * const hasPermission = await checkPermission(ctx.auth.userId, 'posts:read');
275
+ * return hasPermission;
276
+ * };
277
+ *
278
+ * // String expression for native RLS
279
+ * const nativeCondition: PolicyCondition = 'user_id = current_user_id()';
280
+ * ```
281
+ */
282
+ type PolicyCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> = ((ctx: TCtx) => boolean | Promise<boolean>) | string;
283
+ /**
284
+ * Filter condition that returns WHERE clause conditions
285
+ *
286
+ * Used for filter-type policies that add WHERE conditions to queries.
287
+ * Can be either:
288
+ * - A function that returns an object with column-value pairs
289
+ * - An object mapping column names to context property paths
290
+ *
291
+ * @typeParam TCtx - Policy evaluation context type
292
+ *
293
+ * @example
294
+ * ```typescript
295
+ * // Function-based filter
296
+ * const filter: FilterCondition = (ctx) => ({
297
+ * tenant_id: ctx.auth.tenantId,
298
+ * deleted_at: null
299
+ * });
300
+ *
301
+ * // Static filter mapping
302
+ * const staticFilter: FilterCondition = {
303
+ * tenant_id: 'auth.tenantId',
304
+ * status: 'meta.defaultStatus'
305
+ * };
306
+ * ```
307
+ */
308
+ type FilterCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> = ((ctx: TCtx) => Record<string, unknown>) | Record<string, string>;
309
+ /**
310
+ * Policy behavior type
311
+ *
312
+ * - `allow`: Grants access if condition is true
313
+ * - `deny`: Denies access if condition is true (takes precedence)
314
+ * - `filter`: Adds WHERE conditions to automatically filter rows
315
+ * - `validate`: Validates data during create/update operations
316
+ *
317
+ * @example
318
+ * ```typescript
319
+ * // Allow policy: grants access to owners
320
+ * const allowPolicy: PolicyDefinition = {
321
+ * type: 'allow',
322
+ * operation: 'update',
323
+ * condition: (ctx) => ctx.auth.userId === ctx.row.ownerId
324
+ * };
325
+ *
326
+ * // Deny policy: prevents access to deleted items
327
+ * const denyPolicy: PolicyDefinition = {
328
+ * type: 'deny',
329
+ * operation: 'read',
330
+ * condition: (ctx) => ctx.row.deletedAt !== null
331
+ * };
332
+ *
333
+ * // Filter policy: automatically filters by tenant
334
+ * const filterPolicy: PolicyDefinition = {
335
+ * type: 'filter',
336
+ * operation: 'read',
337
+ * condition: (ctx) => ({ tenant_id: ctx.auth.tenantId })
338
+ * };
339
+ *
340
+ * // Validate policy: ensures valid status transitions
341
+ * const validatePolicy: PolicyDefinition = {
342
+ * type: 'validate',
343
+ * operation: 'update',
344
+ * condition: (ctx) => isValidStatusTransition(ctx.row.status, ctx.data.status)
345
+ * };
346
+ * ```
347
+ */
348
+ type PolicyType = 'allow' | 'deny' | 'filter' | 'validate';
349
+ /**
350
+ * Policy definition for RLS enforcement
351
+ *
352
+ * Defines a single security policy with its behavior, operations, and conditions.
353
+ *
354
+ * @typeParam TOperation - Operation type(s) the policy applies to
355
+ * @typeParam TCondition - Condition type (function or string)
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * // Basic ownership policy
360
+ * const ownershipPolicy: PolicyDefinition = {
361
+ * type: 'allow',
362
+ * operation: ['read', 'update', 'delete'],
363
+ * condition: (ctx) => ctx.auth.userId === ctx.row.ownerId,
364
+ * name: 'ownership_policy',
365
+ * priority: 100
366
+ * };
367
+ *
368
+ * // Native PostgreSQL RLS policy
369
+ * const nativePolicy: PolicyDefinition = {
370
+ * type: 'allow',
371
+ * operation: 'read',
372
+ * condition: '',
373
+ * using: 'user_id = current_user_id()',
374
+ * withCheck: 'user_id = current_user_id()',
375
+ * role: 'authenticated',
376
+ * name: 'user_isolation'
377
+ * };
378
+ *
379
+ * // Multi-tenancy filter
380
+ * const tenantFilter: PolicyDefinition = {
381
+ * type: 'filter',
382
+ * operation: 'read',
383
+ * condition: (ctx) => ({ tenant_id: ctx.auth.tenantId }),
384
+ * name: 'tenant_isolation',
385
+ * priority: 1000 // High priority for tenant isolation
386
+ * };
387
+ * ```
388
+ */
389
+ interface PolicyDefinition<TOperation extends Operation = Operation, TCondition = PolicyCondition> {
390
+ /**
391
+ * Policy behavior type
392
+ * Determines how the policy affects access control
393
+ */
394
+ type: PolicyType;
395
+ /**
396
+ * Operation(s) this policy applies to
397
+ * Can be a single operation or an array of operations
398
+ */
399
+ operation: TOperation | TOperation[];
400
+ /**
401
+ * Condition function or expression
402
+ * - For 'allow'/'deny'/'validate': returns boolean
403
+ * - For 'filter': returns WHERE conditions object
404
+ * - For native RLS: SQL expression string
405
+ */
406
+ condition: TCondition;
407
+ /**
408
+ * Optional policy name for debugging and logging
409
+ * Recommended for easier policy management
410
+ */
411
+ name?: string;
412
+ /**
413
+ * Optional priority for policy evaluation order
414
+ * Higher priority policies are evaluated first
415
+ * Deny policies typically have higher priority
416
+ *
417
+ * @default 0
418
+ */
419
+ priority?: number;
420
+ /**
421
+ * Optional SQL expression for native RLS USING clause
422
+ * PostgreSQL only - used for row visibility checks
423
+ *
424
+ * @example 'user_id = current_user_id()'
425
+ */
426
+ using?: string;
427
+ /**
428
+ * Optional SQL expression for native RLS WITH CHECK clause
429
+ * PostgreSQL only - used for insert/update validation
430
+ *
431
+ * @example 'tenant_id = current_tenant_id()'
432
+ */
433
+ withCheck?: string;
434
+ /**
435
+ * Optional database role this policy applies to
436
+ * PostgreSQL only - restricts policy to specific roles
437
+ *
438
+ * @example 'authenticated'
439
+ */
440
+ role?: string;
441
+ /**
442
+ * Optional performance optimization hints
443
+ * Used for query optimization and index suggestions
444
+ */
445
+ hints?: PolicyHints;
446
+ }
447
+ /**
448
+ * RLS configuration for a single database table
449
+ *
450
+ * Defines all policies and settings for row-level security on a table.
451
+ *
452
+ * @example
453
+ * ```typescript
454
+ * const postsConfig: TableRLSConfig = {
455
+ * policies: [
456
+ * {
457
+ * type: 'filter',
458
+ * operation: 'read',
459
+ * condition: (ctx) => ({ tenant_id: ctx.auth.tenantId }),
460
+ * name: 'tenant_isolation',
461
+ * priority: 1000
462
+ * },
463
+ * {
464
+ * type: 'allow',
465
+ * operation: ['update', 'delete'],
466
+ * condition: (ctx) => ctx.auth.userId === ctx.row.authorId,
467
+ * name: 'author_access'
468
+ * }
469
+ * ],
470
+ * defaultDeny: true,
471
+ * skipFor: ['system', 'admin']
472
+ * };
473
+ * ```
474
+ */
475
+ interface TableRLSConfig {
476
+ /**
477
+ * List of policies to enforce on this table
478
+ * Policies are evaluated in priority order (highest first)
479
+ */
480
+ policies: PolicyDefinition[];
481
+ /**
482
+ * Default behavior when no policies match
483
+ * - true: Deny access by default (secure default)
484
+ * - false: Allow access by default (open default)
485
+ *
486
+ * @default true
487
+ */
488
+ defaultDeny?: boolean;
489
+ /**
490
+ * List of roles that bypass RLS policies
491
+ * Useful for system operations or admin accounts
492
+ *
493
+ * @example ['system', 'admin', 'superuser']
494
+ */
495
+ skipFor?: string[];
496
+ }
497
+ /**
498
+ * Complete RLS schema for all tables in the database
499
+ *
500
+ * Maps table names to their RLS configurations.
501
+ *
502
+ * @typeParam DB - Database schema type (Kysely DB type)
503
+ *
504
+ * @example
505
+ * ```typescript
506
+ * interface Database {
507
+ * posts: Post;
508
+ * comments: Comment;
509
+ * users: User;
510
+ * }
511
+ *
512
+ * const rlsSchema: RLSSchema<Database> = {
513
+ * posts: {
514
+ * policies: [
515
+ * {
516
+ * type: 'filter',
517
+ * operation: 'read',
518
+ * condition: (ctx) => ({ tenant_id: ctx.auth.tenantId })
519
+ * }
520
+ * ],
521
+ * defaultDeny: true
522
+ * },
523
+ * comments: {
524
+ * policies: [
525
+ * {
526
+ * type: 'allow',
527
+ * operation: 'all',
528
+ * condition: (ctx) => ctx.auth.roles.includes('moderator')
529
+ * }
530
+ * ]
531
+ * }
532
+ * };
533
+ * ```
534
+ */
535
+ type RLSSchema<DB> = {
536
+ [K in keyof DB]?: TableRLSConfig;
537
+ };
538
+ /**
539
+ * Compiled policy ready for runtime evaluation
540
+ *
541
+ * Internal representation of a policy after compilation and optimization.
542
+ *
543
+ * @typeParam TCtx - Policy evaluation context type
544
+ */
545
+ interface CompiledPolicy<TCtx = PolicyEvaluationContext> {
546
+ /**
547
+ * Policy behavior type
548
+ */
549
+ type: PolicyType;
550
+ /**
551
+ * Operations this policy applies to
552
+ * Always an array after compilation
553
+ */
554
+ operation: Operation[];
555
+ /**
556
+ * Compiled evaluation function
557
+ * Returns boolean or Promise<boolean>
558
+ */
559
+ evaluate: (ctx: TCtx) => boolean | Promise<boolean>;
560
+ /**
561
+ * Policy name for debugging
562
+ */
563
+ name: string;
564
+ /**
565
+ * Priority for evaluation order
566
+ */
567
+ priority: number;
568
+ }
569
+ /**
570
+ * Compiled filter policy for query transformation
571
+ *
572
+ * Specialized compiled policy type for filter operations.
573
+ *
574
+ * @typeParam TCtx - Policy evaluation context type
575
+ */
576
+ interface CompiledFilterPolicy<TCtx = PolicyEvaluationContext> {
577
+ /**
578
+ * Always 'read' for filter policies
579
+ */
580
+ operation: 'read';
581
+ /**
582
+ * Function to get WHERE conditions
583
+ * Returns object with column-value pairs
584
+ */
585
+ getConditions: (ctx: TCtx) => Record<string, unknown>;
586
+ /**
587
+ * Policy name for debugging
588
+ */
589
+ name: string;
590
+ }
591
+ /**
592
+ * Optimization hints for policy execution
593
+ *
594
+ * Provides metadata to help optimize policy evaluation and query generation.
595
+ * These hints can be used by query optimizers and execution planners.
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * const hints: PolicyHints = {
600
+ * indexColumns: ['tenant_id', 'user_id'],
601
+ * selectivity: 'high',
602
+ * leakproof: true,
603
+ * stable: true
604
+ * };
605
+ * ```
606
+ */
607
+ interface PolicyHints {
608
+ /**
609
+ * Columns that should be indexed for optimal performance
610
+ * Suggests which columns are frequently used in policy conditions
611
+ */
612
+ indexColumns?: string[];
613
+ /**
614
+ * Expected selectivity of the policy
615
+ * - 'high': Filters out most rows (good for early evaluation)
616
+ * - 'medium': Filters moderate number of rows
617
+ * - 'low': Filters few rows (evaluate later)
618
+ */
619
+ selectivity?: 'high' | 'medium' | 'low';
620
+ /**
621
+ * Whether the policy is leakproof (PostgreSQL concept)
622
+ * Leakproof functions don't reveal information about their inputs
623
+ * Safe to execute before other security checks
624
+ */
625
+ leakproof?: boolean;
626
+ /**
627
+ * Whether the policy result is stable for the same inputs
628
+ * Stable policies can be cached during a query execution
629
+ */
630
+ stable?: boolean;
631
+ }
632
+
633
+ export type { CompiledPolicy as C, FilterCondition as F, Operation as O, PolicyCondition as P, RLSSchema as R, TableRLSConfig as T, PolicyHints as a, PolicyDefinition as b, CompiledFilterPolicy as c, RLSContext as d, RLSAuthContext as e, RLSRequestContext as f, PolicyEvaluationContext as g, PolicyType as h };