@kysera/rls 0.8.6 → 0.8.8

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RLSSchema, O as Operation, P as PolicyCondition, a as PolicyHints, b as PolicyActivationCondition, C as ConditionalPolicyDefinition, F as FilterCondition, T as TableRLSConfig, c as PolicyDefinition, d as CompiledPolicy, e as CompiledFilterPolicy, f as RLSContext, g as RLSAuthContext, h as RLSRequestContext, i as PolicyEvaluationContext } from './types-CyqksFKU.js';
2
- export { k as PolicyActivationContext, j as PolicyType } from './types-CyqksFKU.js';
1
+ import { R as RLSSchema, O as Operation, P as PolicyCondition, a as PolicyHints, b as PolicyActivationCondition, C as ConditionalPolicyDefinition, F as FilterCondition, T as TableRLSConfig, c as PolicyDefinition, d as CompiledPolicy, e as CompiledFilterPolicy, f as RLSContext, g as RLSAuthContext, h as RLSRequestContext, i as PolicyEvaluationContext } from './types-D3hQINlj.js';
2
+ export { k as PolicyActivationContext, j as PolicyType } from './types-D3hQINlj.js';
3
3
  import { KyseraLogger, DatabaseError, ErrorCode } from '@kysera/core';
4
4
  import { Plugin } from '@kysera/executor';
5
5
  import { z } from 'zod';
@@ -663,8 +663,14 @@ interface RLSPluginOptions<DB = unknown> {
663
663
  /** RLS policy schema */
664
664
  schema: RLSSchema<DB>;
665
665
  /**
666
- * Tables to exclude from RLS (always bypass policies)
667
- * @default []
666
+ * Whitelist of tables to apply RLS to.
667
+ * If provided, only these tables will have RLS enforced.
668
+ * Takes precedence over excludeTables when both are provided.
669
+ */
670
+ tables?: string[];
671
+ /**
672
+ * Tables to exclude from RLS (always bypass policies).
673
+ * Ignored if `tables` whitelist is provided.
668
674
  */
669
675
  excludeTables?: string[];
670
676
  /** Roles that bypass RLS entirely (e.g., ['admin', 'superuser']) */
@@ -719,6 +725,7 @@ interface RLSPluginOptions<DB = unknown> {
719
725
  * Note: 'schema' and 'onViolation' are not included as they are complex runtime objects.
720
726
  */
721
727
  declare const RLSPluginOptionsSchema: z.ZodObject<{
728
+ tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
722
729
  excludeTables: z.ZodOptional<z.ZodArray<z.ZodString>>;
723
730
  bypassRoles: z.ZodOptional<z.ZodArray<z.ZodString>>;
724
731
  requireContext: z.ZodOptional<z.ZodBoolean>;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { DatabaseError, silentLogger } from '@kysera/core';
1
+ import { DatabaseError, silentLogger, shouldApplyToTable } from '@kysera/core';
2
2
  import { isRepositoryLike, getRawDb } from '@kysera/executor';
3
3
  import { z } from 'zod';
4
- import { AsyncLocalStorage } from 'async_hooks';
4
+ import { AsyncLocalStorage } from 'node:async_hooks';
5
5
  import { sql } from 'kysely';
6
6
 
7
7
  // src/errors.ts
@@ -1173,9 +1173,9 @@ var MutationGuard = class {
1173
1173
  if (skipFor.some((role) => ctx.auth.roles.includes(role))) {
1174
1174
  return;
1175
1175
  }
1176
+ const evalCtx = this.createEvalContext(ctx, table, operation, row, data);
1176
1177
  const denies = this.registry.getDenies(table, operation);
1177
1178
  for (const deny2 of denies) {
1178
- const evalCtx = this.createEvalContext(ctx, table, operation, row, data);
1179
1179
  const result = await this.evaluatePolicy(deny2.evaluate, evalCtx, deny2.name);
1180
1180
  if (result) {
1181
1181
  throw new RLSPolicyViolation(operation, table, `Denied by policy: ${deny2.name}`);
@@ -1184,7 +1184,6 @@ var MutationGuard = class {
1184
1184
  if ((operation === "create" || operation === "update") && data) {
1185
1185
  const validates = this.registry.getValidates(table, operation);
1186
1186
  for (const validate2 of validates) {
1187
- const evalCtx = this.createEvalContext(ctx, table, operation, row, data);
1188
1187
  const result = await this.evaluatePolicy(validate2.evaluate, evalCtx, validate2.name);
1189
1188
  if (!result) {
1190
1189
  throw new RLSPolicyViolation(operation, table, `Validation failed: ${validate2.name}`);
@@ -1199,7 +1198,6 @@ var MutationGuard = class {
1199
1198
  if (allows.length > 0) {
1200
1199
  let allowed = false;
1201
1200
  for (const allow2 of allows) {
1202
- const evalCtx = this.createEvalContext(ctx, table, operation, row, data);
1203
1201
  const result = await this.evaluatePolicy(allow2.evaluate, evalCtx, allow2.name);
1204
1202
  if (result) {
1205
1203
  allowed = true;
@@ -1292,6 +1290,7 @@ var MutationGuard = class {
1292
1290
  var RAW_VERSION = "__VERSION__";
1293
1291
  var VERSION = RAW_VERSION.startsWith("__") ? "0.0.0-dev" : RAW_VERSION;
1294
1292
  var RLSPluginOptionsSchema = z.object({
1293
+ tables: z.array(z.string()).optional(),
1295
1294
  excludeTables: z.array(z.string()).optional(),
1296
1295
  bypassRoles: z.array(z.string()).optional(),
1297
1296
  requireContext: z.boolean().optional(),
@@ -1302,7 +1301,8 @@ var RLSPluginOptionsSchema = z.object({
1302
1301
  function rlsPlugin(options) {
1303
1302
  const {
1304
1303
  schema,
1305
- excludeTables = [],
1304
+ tables,
1305
+ excludeTables,
1306
1306
  bypassRoles = [],
1307
1307
  logger = silentLogger,
1308
1308
  requireContext = true,
@@ -1319,8 +1319,8 @@ function rlsPlugin(options) {
1319
1319
  return {
1320
1320
  name: "@kysera/rls",
1321
1321
  version: VERSION,
1322
- // Run after soft-delete (priority 0), before audit
1323
- priority: 50,
1322
+ // SECURITY plugin: must run FIRST to enforce access policies before other plugins
1323
+ priority: 1e3,
1324
1324
  // No dependencies by default
1325
1325
  dependencies: [],
1326
1326
  /**
@@ -1329,7 +1329,7 @@ function rlsPlugin(options) {
1329
1329
  onInit(_executor) {
1330
1330
  logger.info?.("[RLS] Initializing RLS plugin", {
1331
1331
  tables: Object.keys(schema).length,
1332
- excludeTables: excludeTables.length,
1332
+ excludeTables: excludeTables?.length ?? 0,
1333
1333
  bypassRoles: bypassRoles.length
1334
1334
  });
1335
1335
  registry = new PolicyRegistry(schema);
@@ -1344,7 +1344,6 @@ function rlsPlugin(options) {
1344
1344
  onDestroy() {
1345
1345
  registry.clear();
1346
1346
  logger.info?.("[RLS] RLS plugin destroyed, cleared policy registry");
1347
- return Promise.resolve();
1348
1347
  },
1349
1348
  /**
1350
1349
  * Intercept queries to apply RLS filtering
@@ -1355,7 +1354,7 @@ function rlsPlugin(options) {
1355
1354
  */
1356
1355
  interceptQuery(qb, context) {
1357
1356
  const { operation, table, metadata } = context;
1358
- if (excludeTables.includes(table)) {
1357
+ if (!shouldApplyToTable(table, { tables, excludeTables })) {
1359
1358
  logger.debug?.(`[RLS] Skipping RLS for excluded table: ${table}`);
1360
1359
  return qb;
1361
1360
  }
@@ -1438,7 +1437,7 @@ function rlsPlugin(options) {
1438
1437
  }
1439
1438
  const baseRepo = repo;
1440
1439
  const table = baseRepo.tableName;
1441
- if (excludeTables.includes(table)) {
1440
+ if (!shouldApplyToTable(table, { tables, excludeTables })) {
1442
1441
  logger.debug?.(`[RLS] Skipping repository extension for excluded table: ${table}`);
1443
1442
  return repo;
1444
1443
  }