@kysera/rls 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +389 -279
- package/dist/index.d.ts +89 -20
- package/dist/index.js +210 -130
- package/dist/index.js.map +1 -1
- package/dist/native/index.d.ts +1 -1
- package/dist/native/index.js +3 -14
- package/dist/native/index.js.map +1 -1
- package/dist/{types-6eCXh_Jd.d.ts → types-Dowjd6zG.d.ts} +3 -3
- package/package.json +16 -7
- package/src/context/index.ts +4 -4
- package/src/context/manager.ts +45 -45
- package/src/context/storage.ts +3 -3
- package/src/context/types.ts +1 -5
- package/src/errors.ts +62 -77
- package/src/index.ts +13 -13
- package/src/native/README.md +49 -46
- package/src/native/index.ts +3 -6
- package/src/native/migration.ts +29 -27
- package/src/native/postgres.ts +63 -74
- package/src/plugin.ts +286 -159
- package/src/policy/builder.ts +46 -33
- package/src/policy/index.ts +4 -4
- package/src/policy/registry.ts +100 -105
- package/src/policy/schema.ts +58 -71
- package/src/policy/types.ts +58 -58
- package/src/transformer/index.ts +2 -2
- package/src/transformer/mutation.ts +95 -98
- package/src/transformer/select.ts +59 -43
- package/src/utils/helpers.ts +57 -50
- package/src/utils/index.ts +13 -2
- package/src/utils/type-utils.ts +155 -0
- package/src/version.ts +7 -0
package/src/policy/schema.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Provides functions to define, validate, and merge RLS schemas.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { RLSSchema, TableRLSConfig, PolicyDefinition } from './types.js'
|
|
8
|
-
import { RLSSchemaError } from '../errors.js'
|
|
7
|
+
import type { RLSSchema, TableRLSConfig, PolicyDefinition } from './types.js'
|
|
8
|
+
import { RLSSchemaError } from '../errors.js'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Define RLS schema with full type safety
|
|
@@ -42,12 +42,10 @@ import { RLSSchemaError } from '../errors.js';
|
|
|
42
42
|
* });
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
|
-
export function defineRLSSchema<DB>(
|
|
46
|
-
schema: RLSSchema<DB>
|
|
47
|
-
): RLSSchema<DB> {
|
|
45
|
+
export function defineRLSSchema<DB>(schema: RLSSchema<DB>): RLSSchema<DB> {
|
|
48
46
|
// Validate schema
|
|
49
|
-
validateSchema(schema)
|
|
50
|
-
return schema
|
|
47
|
+
validateSchema(schema)
|
|
48
|
+
return schema
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
/**
|
|
@@ -58,22 +56,19 @@ export function defineRLSSchema<DB>(
|
|
|
58
56
|
*/
|
|
59
57
|
function validateSchema<DB>(schema: RLSSchema<DB>): void {
|
|
60
58
|
for (const [table, config] of Object.entries(schema)) {
|
|
61
|
-
if (!config) continue
|
|
59
|
+
if (!config) continue
|
|
62
60
|
|
|
63
|
-
const tableConfig = config as TableRLSConfig
|
|
61
|
+
const tableConfig = config as TableRLSConfig
|
|
64
62
|
|
|
65
63
|
if (!Array.isArray(tableConfig.policies)) {
|
|
66
|
-
throw new RLSSchemaError(
|
|
67
|
-
`Invalid policies for table "${table}": must be an array`,
|
|
68
|
-
{ table }
|
|
69
|
-
);
|
|
64
|
+
throw new RLSSchemaError(`Invalid policies for table "${table}": must be an array`, { table })
|
|
70
65
|
}
|
|
71
66
|
|
|
72
67
|
// Validate each policy
|
|
73
68
|
for (let i = 0; i < tableConfig.policies.length; i++) {
|
|
74
|
-
const policy = tableConfig.policies[i]
|
|
69
|
+
const policy = tableConfig.policies[i]
|
|
75
70
|
if (policy !== undefined) {
|
|
76
|
-
validatePolicy(policy, table, i)
|
|
71
|
+
validatePolicy(policy, table, i)
|
|
77
72
|
}
|
|
78
73
|
}
|
|
79
74
|
|
|
@@ -83,7 +78,7 @@ function validateSchema<DB>(schema: RLSSchema<DB>): void {
|
|
|
83
78
|
throw new RLSSchemaError(
|
|
84
79
|
`Invalid skipFor for table "${table}": must be an array of role names`,
|
|
85
80
|
{ table }
|
|
86
|
-
)
|
|
81
|
+
)
|
|
87
82
|
}
|
|
88
83
|
|
|
89
84
|
// skipFor contains role names (strings), not operations
|
|
@@ -92,17 +87,16 @@ function validateSchema<DB>(schema: RLSSchema<DB>): void {
|
|
|
92
87
|
throw new RLSSchemaError(
|
|
93
88
|
`Invalid role in skipFor for table "${table}": must be a non-empty string`,
|
|
94
89
|
{ table }
|
|
95
|
-
)
|
|
90
|
+
)
|
|
96
91
|
}
|
|
97
92
|
}
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
// Validate defaultDeny if present
|
|
101
96
|
if (tableConfig.defaultDeny !== undefined && typeof tableConfig.defaultDeny !== 'boolean') {
|
|
102
|
-
throw new RLSSchemaError(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
97
|
+
throw new RLSSchemaError(`Invalid defaultDeny for table "${table}": must be a boolean`, {
|
|
98
|
+
table
|
|
99
|
+
})
|
|
106
100
|
}
|
|
107
101
|
}
|
|
108
102
|
}
|
|
@@ -113,73 +107,66 @@ function validateSchema<DB>(schema: RLSSchema<DB>): void {
|
|
|
113
107
|
*
|
|
114
108
|
* @internal
|
|
115
109
|
*/
|
|
116
|
-
function validatePolicy(
|
|
117
|
-
policy: PolicyDefinition,
|
|
118
|
-
table: string,
|
|
119
|
-
index: number
|
|
120
|
-
): void {
|
|
110
|
+
function validatePolicy(policy: PolicyDefinition, table: string, index: number): void {
|
|
121
111
|
if (!policy.type) {
|
|
122
|
-
throw new RLSSchemaError(
|
|
123
|
-
`Policy ${index} for table "${table}" missing type`,
|
|
124
|
-
{ table, index }
|
|
125
|
-
);
|
|
112
|
+
throw new RLSSchemaError(`Policy ${index} for table "${table}" missing type`, { table, index })
|
|
126
113
|
}
|
|
127
114
|
|
|
128
|
-
const validTypes = ['allow', 'deny', 'filter', 'validate']
|
|
115
|
+
const validTypes = ['allow', 'deny', 'filter', 'validate']
|
|
129
116
|
if (!validTypes.includes(policy.type)) {
|
|
130
117
|
throw new RLSSchemaError(
|
|
131
118
|
`Policy ${index} for table "${table}" has invalid type: ${policy.type}`,
|
|
132
119
|
{ table, index, type: policy.type }
|
|
133
|
-
)
|
|
120
|
+
)
|
|
134
121
|
}
|
|
135
122
|
|
|
136
123
|
if (!policy.operation) {
|
|
137
|
-
throw new RLSSchemaError(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
124
|
+
throw new RLSSchemaError(`Policy ${index} for table "${table}" missing operation`, {
|
|
125
|
+
table,
|
|
126
|
+
index
|
|
127
|
+
})
|
|
141
128
|
}
|
|
142
129
|
|
|
143
|
-
const validOps = ['read', 'create', 'update', 'delete', 'all']
|
|
144
|
-
const ops = Array.isArray(policy.operation) ? policy.operation : [policy.operation]
|
|
130
|
+
const validOps = ['read', 'create', 'update', 'delete', 'all']
|
|
131
|
+
const ops = Array.isArray(policy.operation) ? policy.operation : [policy.operation]
|
|
145
132
|
|
|
146
133
|
for (const op of ops) {
|
|
147
134
|
if (!validOps.includes(op)) {
|
|
148
135
|
throw new RLSSchemaError(
|
|
149
136
|
`Policy ${index} for table "${table}" has invalid operation: ${op}`,
|
|
150
137
|
{ table, index, operation: op }
|
|
151
|
-
)
|
|
138
|
+
)
|
|
152
139
|
}
|
|
153
140
|
}
|
|
154
141
|
|
|
155
142
|
if (policy.condition === undefined || policy.condition === null) {
|
|
156
|
-
throw new RLSSchemaError(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
143
|
+
throw new RLSSchemaError(`Policy ${index} for table "${table}" missing condition`, {
|
|
144
|
+
table,
|
|
145
|
+
index
|
|
146
|
+
})
|
|
160
147
|
}
|
|
161
148
|
|
|
162
149
|
if (typeof policy.condition !== 'function' && typeof policy.condition !== 'string') {
|
|
163
150
|
throw new RLSSchemaError(
|
|
164
151
|
`Policy ${index} for table "${table}" condition must be a function or string`,
|
|
165
152
|
{ table, index }
|
|
166
|
-
)
|
|
153
|
+
)
|
|
167
154
|
}
|
|
168
155
|
|
|
169
156
|
// Validate priority if present
|
|
170
157
|
if (policy.priority !== undefined && typeof policy.priority !== 'number') {
|
|
171
|
-
throw new RLSSchemaError(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)
|
|
158
|
+
throw new RLSSchemaError(`Policy ${index} for table "${table}" priority must be a number`, {
|
|
159
|
+
table,
|
|
160
|
+
index
|
|
161
|
+
})
|
|
175
162
|
}
|
|
176
163
|
|
|
177
164
|
// Validate name if present
|
|
178
165
|
if (policy.name !== undefined && typeof policy.name !== 'string') {
|
|
179
|
-
throw new RLSSchemaError(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
)
|
|
166
|
+
throw new RLSSchemaError(`Policy ${index} for table "${table}" name must be a string`, {
|
|
167
|
+
table,
|
|
168
|
+
index
|
|
169
|
+
})
|
|
183
170
|
}
|
|
184
171
|
}
|
|
185
172
|
|
|
@@ -209,49 +196,49 @@ function validatePolicy(
|
|
|
209
196
|
* const merged = mergeRLSSchemas(baseSchema, adminSchema);
|
|
210
197
|
* ```
|
|
211
198
|
*/
|
|
212
|
-
export function mergeRLSSchemas<DB>(
|
|
213
|
-
|
|
214
|
-
): RLSSchema<DB> {
|
|
215
|
-
const merged: RLSSchema<DB> = {};
|
|
199
|
+
export function mergeRLSSchemas<DB>(...schemas: RLSSchema<DB>[]): RLSSchema<DB> {
|
|
200
|
+
const merged: RLSSchema<DB> = {}
|
|
216
201
|
|
|
217
202
|
for (const schema of schemas) {
|
|
218
203
|
for (const [table, config] of Object.entries(schema)) {
|
|
219
|
-
if (!config) continue
|
|
204
|
+
if (!config) continue
|
|
220
205
|
|
|
221
|
-
const existingConfig = merged[table as keyof DB]
|
|
222
|
-
const newConfig = config as TableRLSConfig
|
|
206
|
+
const existingConfig = merged[table as keyof DB]
|
|
207
|
+
const newConfig = config as TableRLSConfig
|
|
223
208
|
|
|
224
209
|
if (existingConfig) {
|
|
225
210
|
// Merge policies (append new policies)
|
|
226
|
-
existingConfig.policies = [
|
|
227
|
-
...existingConfig.policies,
|
|
228
|
-
...newConfig.policies,
|
|
229
|
-
];
|
|
211
|
+
existingConfig.policies = [...existingConfig.policies, ...newConfig.policies]
|
|
230
212
|
|
|
231
213
|
// Merge skipFor (combine arrays and deduplicate)
|
|
232
214
|
if (newConfig.skipFor) {
|
|
233
|
-
const existingSkipFor = existingConfig.skipFor ?? []
|
|
234
|
-
const combinedSkipFor = [...existingSkipFor, ...newConfig.skipFor]
|
|
235
|
-
existingConfig.skipFor = Array.from(new Set(combinedSkipFor))
|
|
215
|
+
const existingSkipFor = existingConfig.skipFor ?? []
|
|
216
|
+
const combinedSkipFor = [...existingSkipFor, ...newConfig.skipFor]
|
|
217
|
+
existingConfig.skipFor = Array.from(new Set(combinedSkipFor))
|
|
236
218
|
}
|
|
237
219
|
|
|
238
220
|
// Override defaultDeny if explicitly set in new config
|
|
239
221
|
if (newConfig.defaultDeny !== undefined) {
|
|
240
|
-
existingConfig.defaultDeny = newConfig.defaultDeny
|
|
222
|
+
existingConfig.defaultDeny = newConfig.defaultDeny
|
|
241
223
|
}
|
|
242
224
|
} else {
|
|
243
225
|
// Deep copy the config to avoid mutation
|
|
226
|
+
// Type cast necessary: TypeScript cannot infer that the spread object matches
|
|
227
|
+
// the TableRLSConfig type exactly due to optional properties and type narrowing.
|
|
228
|
+
// Runtime safety: We've validated the schema structure via validateSchema(),
|
|
229
|
+
// and we're creating a proper TableRLSConfig from validated components.
|
|
230
|
+
|
|
244
231
|
merged[table as keyof DB] = {
|
|
245
232
|
policies: [...newConfig.policies],
|
|
246
233
|
skipFor: newConfig.skipFor ? [...newConfig.skipFor] : undefined,
|
|
247
|
-
defaultDeny: newConfig.defaultDeny
|
|
248
|
-
} as
|
|
234
|
+
defaultDeny: newConfig.defaultDeny
|
|
235
|
+
} as TableRLSConfig
|
|
249
236
|
}
|
|
250
237
|
}
|
|
251
238
|
}
|
|
252
239
|
|
|
253
240
|
// Validate merged schema
|
|
254
|
-
validateSchema(merged)
|
|
241
|
+
validateSchema(merged)
|
|
255
242
|
|
|
256
|
-
return merged
|
|
243
|
+
return merged
|
|
257
244
|
}
|
package/src/policy/types.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module @kysera/rls/policy/types
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { Kysely } from 'kysely'
|
|
11
|
+
import type { Kysely } from 'kysely'
|
|
12
12
|
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// Operation Types
|
|
@@ -32,7 +32,7 @@ import type { Kysely } from 'kysely';
|
|
|
32
32
|
* };
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
-
export type Operation = 'read' | 'create' | 'update' | 'delete' | 'all'
|
|
35
|
+
export type Operation = 'read' | 'create' | 'update' | 'delete' | 'all'
|
|
36
36
|
|
|
37
37
|
// ============================================================================
|
|
38
38
|
// Authentication Context
|
|
@@ -63,43 +63,43 @@ export interface RLSAuthContext<TUser = unknown> {
|
|
|
63
63
|
* Unique identifier for the authenticated user
|
|
64
64
|
* Can be a string or number depending on your ID strategy
|
|
65
65
|
*/
|
|
66
|
-
userId: string | number
|
|
66
|
+
userId: string | number
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* List of roles assigned to the user
|
|
70
70
|
* Used for role-based access control (RBAC)
|
|
71
71
|
*/
|
|
72
|
-
roles: string[]
|
|
72
|
+
roles: string[]
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Optional tenant identifier for multi-tenancy
|
|
76
76
|
* Use for tenant isolation in SaaS applications
|
|
77
77
|
*/
|
|
78
|
-
tenantId?: string | number
|
|
78
|
+
tenantId?: string | number
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Optional list of organization IDs the user belongs to
|
|
82
82
|
* Use for hierarchical multi-tenancy or organization-based access
|
|
83
83
|
*/
|
|
84
|
-
organizationIds?: (string | number)[]
|
|
84
|
+
organizationIds?: (string | number)[]
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Optional list of granular permissions
|
|
88
88
|
* Use for fine-grained access control
|
|
89
89
|
*/
|
|
90
|
-
permissions?: string[]
|
|
90
|
+
permissions?: string[]
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* Optional custom attributes for advanced policy logic
|
|
94
94
|
* Can contain any additional context needed for policy evaluation
|
|
95
95
|
*/
|
|
96
|
-
attributes?: Record<string, unknown
|
|
96
|
+
attributes?: Record<string, unknown>
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Optional full user object for accessing user properties
|
|
100
100
|
* Useful when policies need to check user-specific attributes
|
|
101
101
|
*/
|
|
102
|
-
user?: TUser
|
|
102
|
+
user?: TUser
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Flag indicating if this is a system/admin context
|
|
@@ -107,7 +107,7 @@ export interface RLSAuthContext<TUser = unknown> {
|
|
|
107
107
|
*
|
|
108
108
|
* @default false
|
|
109
109
|
*/
|
|
110
|
-
isSystem?: boolean
|
|
110
|
+
isSystem?: boolean
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// ============================================================================
|
|
@@ -136,31 +136,31 @@ export interface RLSRequestContext {
|
|
|
136
136
|
* Unique identifier for the request
|
|
137
137
|
* Useful for tracing and debugging
|
|
138
138
|
*/
|
|
139
|
-
requestId?: string
|
|
139
|
+
requestId?: string
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
142
|
* Client IP address
|
|
143
143
|
* Can be used for IP-based access policies
|
|
144
144
|
*/
|
|
145
|
-
ipAddress?: string
|
|
145
|
+
ipAddress?: string
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
148
|
* Client user agent string
|
|
149
149
|
* Useful for device-based access policies
|
|
150
150
|
*/
|
|
151
|
-
userAgent?: string
|
|
151
|
+
userAgent?: string
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
154
|
* Request timestamp
|
|
155
155
|
* Required for time-based policies and audit logs
|
|
156
156
|
*/
|
|
157
|
-
timestamp: Date
|
|
157
|
+
timestamp: Date
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
160
|
* HTTP headers
|
|
161
161
|
* Can contain custom authentication or context headers
|
|
162
162
|
*/
|
|
163
|
-
headers?: Record<string, string
|
|
163
|
+
headers?: Record<string, string>
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// ============================================================================
|
|
@@ -201,26 +201,26 @@ export interface RLSContext<TUser = unknown, TMeta = unknown> {
|
|
|
201
201
|
* Authentication context (required)
|
|
202
202
|
* Contains user identity and authorization information
|
|
203
203
|
*/
|
|
204
|
-
auth: RLSAuthContext<TUser
|
|
204
|
+
auth: RLSAuthContext<TUser>
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* Request context (optional)
|
|
208
208
|
* Contains HTTP request information
|
|
209
209
|
*/
|
|
210
|
-
request?: RLSRequestContext
|
|
210
|
+
request?: RLSRequestContext
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
213
|
* Custom metadata (optional)
|
|
214
214
|
* Can contain any additional context needed for policy evaluation
|
|
215
215
|
* Examples: feature flags, A/B test groups, regional settings
|
|
216
216
|
*/
|
|
217
|
-
meta?: TMeta
|
|
217
|
+
meta?: TMeta
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
220
|
* Context creation timestamp
|
|
221
221
|
* Used for temporal policies and audit trails
|
|
222
222
|
*/
|
|
223
|
-
timestamp: Date
|
|
223
|
+
timestamp: Date
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
// ============================================================================
|
|
@@ -237,7 +237,7 @@ export interface RLSContext<TUser = unknown, TMeta = unknown> {
|
|
|
237
237
|
* @typeParam TAuth - Custom user type for auth context
|
|
238
238
|
* @typeParam TRow - Type of the database row being evaluated
|
|
239
239
|
* @typeParam TData - Type of the data being inserted/updated
|
|
240
|
-
* @typeParam
|
|
240
|
+
* @typeParam DB - Database schema type for Kysely
|
|
241
241
|
*
|
|
242
242
|
* @example
|
|
243
243
|
* ```typescript
|
|
@@ -259,58 +259,58 @@ export interface PolicyEvaluationContext<
|
|
|
259
259
|
TAuth = unknown,
|
|
260
260
|
TRow = unknown,
|
|
261
261
|
TData = unknown,
|
|
262
|
-
|
|
262
|
+
DB = unknown
|
|
263
263
|
> {
|
|
264
264
|
/**
|
|
265
265
|
* Authentication context
|
|
266
266
|
* Contains user identity and authorization information
|
|
267
267
|
*/
|
|
268
|
-
auth: RLSAuthContext<TAuth
|
|
268
|
+
auth: RLSAuthContext<TAuth>
|
|
269
269
|
|
|
270
270
|
/**
|
|
271
271
|
* Current row being evaluated (optional)
|
|
272
272
|
* Available during read/update/delete operations
|
|
273
273
|
* Used for row-level policies that check row attributes
|
|
274
274
|
*/
|
|
275
|
-
row?: TRow
|
|
275
|
+
row?: TRow
|
|
276
276
|
|
|
277
277
|
/**
|
|
278
278
|
* Data being inserted or updated (optional)
|
|
279
279
|
* Available during create/update operations
|
|
280
280
|
* Used for validation policies
|
|
281
281
|
*/
|
|
282
|
-
data?: TData
|
|
282
|
+
data?: TData
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
285
|
* Request context (optional)
|
|
286
286
|
* Contains HTTP request information
|
|
287
287
|
*/
|
|
288
|
-
request?: RLSRequestContext
|
|
288
|
+
request?: RLSRequestContext
|
|
289
289
|
|
|
290
290
|
/**
|
|
291
291
|
* Kysely database instance (optional)
|
|
292
292
|
* Available for policies that need to perform additional queries
|
|
293
293
|
* Use sparingly as it can impact performance
|
|
294
294
|
*/
|
|
295
|
-
db?: Kysely<
|
|
295
|
+
db?: Kysely<DB>
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
298
|
* Custom metadata (optional)
|
|
299
299
|
* Can contain any additional context needed for policy evaluation
|
|
300
300
|
*/
|
|
301
|
-
meta?: Record<string, unknown
|
|
301
|
+
meta?: Record<string, unknown>
|
|
302
302
|
|
|
303
303
|
/**
|
|
304
304
|
* Table name being accessed (optional)
|
|
305
305
|
* Available during mutation operations
|
|
306
306
|
*/
|
|
307
|
-
table?: string
|
|
307
|
+
table?: string
|
|
308
308
|
|
|
309
309
|
/**
|
|
310
310
|
* Operation being performed (optional)
|
|
311
311
|
* E.g., 'create', 'update', 'delete'
|
|
312
312
|
*/
|
|
313
|
-
operation?: string
|
|
313
|
+
operation?: string
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
// ============================================================================
|
|
@@ -346,7 +346,7 @@ export interface PolicyEvaluationContext<
|
|
|
346
346
|
*/
|
|
347
347
|
export type PolicyCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> =
|
|
348
348
|
| ((ctx: TCtx) => boolean | Promise<boolean>)
|
|
349
|
-
| string
|
|
349
|
+
| string
|
|
350
350
|
|
|
351
351
|
/**
|
|
352
352
|
* Filter condition that returns WHERE clause conditions
|
|
@@ -375,7 +375,7 @@ export type PolicyCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluat
|
|
|
375
375
|
*/
|
|
376
376
|
export type FilterCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> =
|
|
377
377
|
| ((ctx: TCtx) => Record<string, unknown>)
|
|
378
|
-
| Record<string, string
|
|
378
|
+
| Record<string, string>
|
|
379
379
|
|
|
380
380
|
// ============================================================================
|
|
381
381
|
// Policy Types
|
|
@@ -420,7 +420,7 @@ export type FilterCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluat
|
|
|
420
420
|
* };
|
|
421
421
|
* ```
|
|
422
422
|
*/
|
|
423
|
-
export type PolicyType = 'allow' | 'deny' | 'filter' | 'validate'
|
|
423
|
+
export type PolicyType = 'allow' | 'deny' | 'filter' | 'validate'
|
|
424
424
|
|
|
425
425
|
// ============================================================================
|
|
426
426
|
// Policy Definition
|
|
@@ -474,13 +474,13 @@ export interface PolicyDefinition<
|
|
|
474
474
|
* Policy behavior type
|
|
475
475
|
* Determines how the policy affects access control
|
|
476
476
|
*/
|
|
477
|
-
type: PolicyType
|
|
477
|
+
type: PolicyType
|
|
478
478
|
|
|
479
479
|
/**
|
|
480
480
|
* Operation(s) this policy applies to
|
|
481
481
|
* Can be a single operation or an array of operations
|
|
482
482
|
*/
|
|
483
|
-
operation: TOperation | TOperation[]
|
|
483
|
+
operation: TOperation | TOperation[]
|
|
484
484
|
|
|
485
485
|
/**
|
|
486
486
|
* Condition function or expression
|
|
@@ -488,13 +488,13 @@ export interface PolicyDefinition<
|
|
|
488
488
|
* - For 'filter': returns WHERE conditions object
|
|
489
489
|
* - For native RLS: SQL expression string
|
|
490
490
|
*/
|
|
491
|
-
condition: TCondition
|
|
491
|
+
condition: TCondition
|
|
492
492
|
|
|
493
493
|
/**
|
|
494
494
|
* Optional policy name for debugging and logging
|
|
495
495
|
* Recommended for easier policy management
|
|
496
496
|
*/
|
|
497
|
-
name?: string
|
|
497
|
+
name?: string
|
|
498
498
|
|
|
499
499
|
/**
|
|
500
500
|
* Optional priority for policy evaluation order
|
|
@@ -503,7 +503,7 @@ export interface PolicyDefinition<
|
|
|
503
503
|
*
|
|
504
504
|
* @default 0
|
|
505
505
|
*/
|
|
506
|
-
priority?: number
|
|
506
|
+
priority?: number
|
|
507
507
|
|
|
508
508
|
/**
|
|
509
509
|
* Optional SQL expression for native RLS USING clause
|
|
@@ -511,7 +511,7 @@ export interface PolicyDefinition<
|
|
|
511
511
|
*
|
|
512
512
|
* @example 'user_id = current_user_id()'
|
|
513
513
|
*/
|
|
514
|
-
using?: string
|
|
514
|
+
using?: string
|
|
515
515
|
|
|
516
516
|
/**
|
|
517
517
|
* Optional SQL expression for native RLS WITH CHECK clause
|
|
@@ -519,7 +519,7 @@ export interface PolicyDefinition<
|
|
|
519
519
|
*
|
|
520
520
|
* @example 'tenant_id = current_tenant_id()'
|
|
521
521
|
*/
|
|
522
|
-
withCheck?: string
|
|
522
|
+
withCheck?: string
|
|
523
523
|
|
|
524
524
|
/**
|
|
525
525
|
* Optional database role this policy applies to
|
|
@@ -527,13 +527,13 @@ export interface PolicyDefinition<
|
|
|
527
527
|
*
|
|
528
528
|
* @example 'authenticated'
|
|
529
529
|
*/
|
|
530
|
-
role?: string
|
|
530
|
+
role?: string
|
|
531
531
|
|
|
532
532
|
/**
|
|
533
533
|
* Optional performance optimization hints
|
|
534
534
|
* Used for query optimization and index suggestions
|
|
535
535
|
*/
|
|
536
|
-
hints?: PolicyHints
|
|
536
|
+
hints?: PolicyHints
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
// ============================================================================
|
|
@@ -573,7 +573,7 @@ export interface TableRLSConfig {
|
|
|
573
573
|
* List of policies to enforce on this table
|
|
574
574
|
* Policies are evaluated in priority order (highest first)
|
|
575
575
|
*/
|
|
576
|
-
policies: PolicyDefinition[]
|
|
576
|
+
policies: PolicyDefinition[]
|
|
577
577
|
|
|
578
578
|
/**
|
|
579
579
|
* Default behavior when no policies match
|
|
@@ -582,7 +582,7 @@ export interface TableRLSConfig {
|
|
|
582
582
|
*
|
|
583
583
|
* @default true
|
|
584
584
|
*/
|
|
585
|
-
defaultDeny?: boolean
|
|
585
|
+
defaultDeny?: boolean
|
|
586
586
|
|
|
587
587
|
/**
|
|
588
588
|
* List of roles that bypass RLS policies
|
|
@@ -590,7 +590,7 @@ export interface TableRLSConfig {
|
|
|
590
590
|
*
|
|
591
591
|
* @example ['system', 'admin', 'superuser']
|
|
592
592
|
*/
|
|
593
|
-
skipFor?: string[]
|
|
593
|
+
skipFor?: string[]
|
|
594
594
|
}
|
|
595
595
|
|
|
596
596
|
// ============================================================================
|
|
@@ -636,8 +636,8 @@ export interface TableRLSConfig {
|
|
|
636
636
|
* ```
|
|
637
637
|
*/
|
|
638
638
|
export type RLSSchema<DB> = {
|
|
639
|
-
[K in keyof DB]?: TableRLSConfig
|
|
640
|
-
}
|
|
639
|
+
[K in keyof DB]?: TableRLSConfig
|
|
640
|
+
}
|
|
641
641
|
|
|
642
642
|
// ============================================================================
|
|
643
643
|
// Compiled Policies
|
|
@@ -654,29 +654,29 @@ export interface CompiledPolicy<TCtx = PolicyEvaluationContext> {
|
|
|
654
654
|
/**
|
|
655
655
|
* Policy behavior type
|
|
656
656
|
*/
|
|
657
|
-
type: PolicyType
|
|
657
|
+
type: PolicyType
|
|
658
658
|
|
|
659
659
|
/**
|
|
660
660
|
* Operations this policy applies to
|
|
661
661
|
* Always an array after compilation
|
|
662
662
|
*/
|
|
663
|
-
operation: Operation[]
|
|
663
|
+
operation: Operation[]
|
|
664
664
|
|
|
665
665
|
/**
|
|
666
666
|
* Compiled evaluation function
|
|
667
667
|
* Returns boolean or Promise<boolean>
|
|
668
668
|
*/
|
|
669
|
-
evaluate: (ctx: TCtx) => boolean | Promise<boolean
|
|
669
|
+
evaluate: (ctx: TCtx) => boolean | Promise<boolean>
|
|
670
670
|
|
|
671
671
|
/**
|
|
672
672
|
* Policy name for debugging
|
|
673
673
|
*/
|
|
674
|
-
name: string
|
|
674
|
+
name: string
|
|
675
675
|
|
|
676
676
|
/**
|
|
677
677
|
* Priority for evaluation order
|
|
678
678
|
*/
|
|
679
|
-
priority: number
|
|
679
|
+
priority: number
|
|
680
680
|
}
|
|
681
681
|
|
|
682
682
|
/**
|
|
@@ -690,18 +690,18 @@ export interface CompiledFilterPolicy<TCtx = PolicyEvaluationContext> {
|
|
|
690
690
|
/**
|
|
691
691
|
* Always 'read' for filter policies
|
|
692
692
|
*/
|
|
693
|
-
operation: 'read'
|
|
693
|
+
operation: 'read'
|
|
694
694
|
|
|
695
695
|
/**
|
|
696
696
|
* Function to get WHERE conditions
|
|
697
697
|
* Returns object with column-value pairs
|
|
698
698
|
*/
|
|
699
|
-
getConditions: (ctx: TCtx) => Record<string, unknown
|
|
699
|
+
getConditions: (ctx: TCtx) => Record<string, unknown>
|
|
700
700
|
|
|
701
701
|
/**
|
|
702
702
|
* Policy name for debugging
|
|
703
703
|
*/
|
|
704
|
-
name: string
|
|
704
|
+
name: string
|
|
705
705
|
}
|
|
706
706
|
|
|
707
707
|
// ============================================================================
|
|
@@ -729,7 +729,7 @@ export interface PolicyHints {
|
|
|
729
729
|
* Columns that should be indexed for optimal performance
|
|
730
730
|
* Suggests which columns are frequently used in policy conditions
|
|
731
731
|
*/
|
|
732
|
-
indexColumns?: string[]
|
|
732
|
+
indexColumns?: string[]
|
|
733
733
|
|
|
734
734
|
/**
|
|
735
735
|
* Expected selectivity of the policy
|
|
@@ -737,18 +737,18 @@ export interface PolicyHints {
|
|
|
737
737
|
* - 'medium': Filters moderate number of rows
|
|
738
738
|
* - 'low': Filters few rows (evaluate later)
|
|
739
739
|
*/
|
|
740
|
-
selectivity?: 'high' | 'medium' | 'low'
|
|
740
|
+
selectivity?: 'high' | 'medium' | 'low'
|
|
741
741
|
|
|
742
742
|
/**
|
|
743
743
|
* Whether the policy is leakproof (PostgreSQL concept)
|
|
744
744
|
* Leakproof functions don't reveal information about their inputs
|
|
745
745
|
* Safe to execute before other security checks
|
|
746
746
|
*/
|
|
747
|
-
leakproof?: boolean
|
|
747
|
+
leakproof?: boolean
|
|
748
748
|
|
|
749
749
|
/**
|
|
750
750
|
* Whether the policy result is stable for the same inputs
|
|
751
751
|
* Stable policies can be cached during a query execution
|
|
752
752
|
*/
|
|
753
|
-
stable?: boolean
|
|
753
|
+
stable?: boolean
|
|
754
754
|
}
|
package/src/transformer/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SelectTransformer } from './select.js'
|
|
2
|
-
export { MutationGuard } from './mutation.js'
|
|
1
|
+
export { SelectTransformer } from './select.js'
|
|
2
|
+
export { MutationGuard } from './mutation.js'
|