@kysera/rls 0.8.5 → 0.8.6
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 +3 -17
- package/dist/index.js +62 -17
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/plugin.ts +12 -2
- package/src/transformer/mutation.ts +50 -15
package/dist/index.d.ts
CHANGED
|
@@ -719,27 +719,13 @@ interface RLSPluginOptions<DB = unknown> {
|
|
|
719
719
|
* Note: 'schema' and 'onViolation' are not included as they are complex runtime objects.
|
|
720
720
|
*/
|
|
721
721
|
declare const RLSPluginOptionsSchema: z.ZodObject<{
|
|
722
|
-
excludeTables: z.ZodOptional<z.ZodArray<z.ZodString
|
|
723
|
-
bypassRoles: z.ZodOptional<z.ZodArray<z.ZodString
|
|
722
|
+
excludeTables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
723
|
+
bypassRoles: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
724
724
|
requireContext: z.ZodOptional<z.ZodBoolean>;
|
|
725
725
|
allowUnfilteredQueries: z.ZodOptional<z.ZodBoolean>;
|
|
726
726
|
auditDecisions: z.ZodOptional<z.ZodBoolean>;
|
|
727
727
|
primaryKeyColumn: z.ZodOptional<z.ZodString>;
|
|
728
|
-
},
|
|
729
|
-
excludeTables?: string[] | undefined;
|
|
730
|
-
bypassRoles?: string[] | undefined;
|
|
731
|
-
requireContext?: boolean | undefined;
|
|
732
|
-
allowUnfilteredQueries?: boolean | undefined;
|
|
733
|
-
auditDecisions?: boolean | undefined;
|
|
734
|
-
primaryKeyColumn?: string | undefined;
|
|
735
|
-
}, {
|
|
736
|
-
excludeTables?: string[] | undefined;
|
|
737
|
-
bypassRoles?: string[] | undefined;
|
|
738
|
-
requireContext?: boolean | undefined;
|
|
739
|
-
allowUnfilteredQueries?: boolean | undefined;
|
|
740
|
-
auditDecisions?: boolean | undefined;
|
|
741
|
-
primaryKeyColumn?: string | undefined;
|
|
742
|
-
}>;
|
|
728
|
+
}, z.core.$strip>;
|
|
743
729
|
/**
|
|
744
730
|
* Create RLS plugin for Kysera
|
|
745
731
|
*
|
package/dist/index.js
CHANGED
|
@@ -997,35 +997,70 @@ var MutationGuard = class {
|
|
|
997
997
|
await this.checkMutation(table, "create", void 0, data);
|
|
998
998
|
}
|
|
999
999
|
/**
|
|
1000
|
-
* Check if UPDATE operation is allowed
|
|
1000
|
+
* Check if an UPDATE operation is allowed by RLS policies.
|
|
1001
1001
|
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1002
|
+
* IMPORTANT: To prevent TOCTOU race conditions, always call this method
|
|
1003
|
+
* within the same transaction as the actual update operation. The existingRow
|
|
1004
|
+
* should be fetched with SELECT FOR UPDATE within the transaction.
|
|
1005
|
+
*
|
|
1006
|
+
* Without proper transaction isolation, the existingRow could be modified by
|
|
1007
|
+
* a concurrent operation between the time it was fetched and the time this
|
|
1008
|
+
* check runs, leading to policy decisions based on stale data.
|
|
1009
|
+
*
|
|
1010
|
+
* @param table - Target table name
|
|
1011
|
+
* @param existingRow - Current row data (should be fetched within same transaction)
|
|
1012
|
+
* @param data - New data being applied
|
|
1013
|
+
* @throws RLSPolicyViolation if the operation is not allowed
|
|
1006
1014
|
*
|
|
1007
1015
|
* @example
|
|
1008
1016
|
* ```typescript
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1017
|
+
* // RECOMMENDED: Use within a transaction with row locking
|
|
1018
|
+
* await db.transaction().execute(async (trx) => {
|
|
1019
|
+
* const existingPost = await trx
|
|
1020
|
+
* .selectFrom('posts')
|
|
1021
|
+
* .where('id', '=', 1)
|
|
1022
|
+
* .forUpdate()
|
|
1023
|
+
* .selectAll()
|
|
1024
|
+
* .executeTakeFirst();
|
|
1025
|
+
*
|
|
1026
|
+
* await guard.checkUpdate('posts', existingPost, { title: 'Updated' });
|
|
1027
|
+
* await trx.updateTable('posts').set({ title: 'Updated' }).where('id', '=', 1).execute();
|
|
1028
|
+
* });
|
|
1012
1029
|
* ```
|
|
1013
1030
|
*/
|
|
1014
1031
|
async checkUpdate(table, existingRow, data) {
|
|
1015
1032
|
await this.checkMutation(table, "update", existingRow, data);
|
|
1016
1033
|
}
|
|
1017
1034
|
/**
|
|
1018
|
-
* Check if DELETE operation is allowed
|
|
1035
|
+
* Check if a DELETE operation is allowed by RLS policies.
|
|
1019
1036
|
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1037
|
+
* IMPORTANT: To prevent TOCTOU race conditions, always call this method
|
|
1038
|
+
* within the same transaction as the actual delete operation. The existingRow
|
|
1039
|
+
* should be fetched with SELECT FOR UPDATE within the transaction.
|
|
1040
|
+
*
|
|
1041
|
+
* Without proper transaction isolation, the existingRow could be modified by
|
|
1042
|
+
* a concurrent operation between the time it was fetched and the time this
|
|
1043
|
+
* check runs, leading to policy decisions based on stale data (e.g., a row's
|
|
1044
|
+
* ownership could change, allowing an unauthorized delete).
|
|
1045
|
+
*
|
|
1046
|
+
* @param table - Target table name
|
|
1047
|
+
* @param existingRow - Current row data (should be fetched within same transaction)
|
|
1048
|
+
* @throws RLSPolicyViolation if the operation is not allowed
|
|
1023
1049
|
*
|
|
1024
1050
|
* @example
|
|
1025
1051
|
* ```typescript
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
1028
|
-
* await
|
|
1052
|
+
* // RECOMMENDED: Use within a transaction with row locking
|
|
1053
|
+
* await db.transaction().execute(async (trx) => {
|
|
1054
|
+
* const existingPost = await trx
|
|
1055
|
+
* .selectFrom('posts')
|
|
1056
|
+
* .where('id', '=', 1)
|
|
1057
|
+
* .forUpdate()
|
|
1058
|
+
* .selectAll()
|
|
1059
|
+
* .executeTakeFirst();
|
|
1060
|
+
*
|
|
1061
|
+
* await guard.checkDelete('posts', existingPost);
|
|
1062
|
+
* await trx.deleteFrom('posts').where('id', '=', 1).execute();
|
|
1063
|
+
* });
|
|
1029
1064
|
* ```
|
|
1030
1065
|
*/
|
|
1031
1066
|
async checkDelete(table, existingRow) {
|
|
@@ -1454,7 +1489,12 @@ function rlsPlugin(options) {
|
|
|
1454
1489
|
return await originalCreate(data);
|
|
1455
1490
|
},
|
|
1456
1491
|
/**
|
|
1457
|
-
* Wrapped update with RLS check
|
|
1492
|
+
* Wrapped update with RLS check.
|
|
1493
|
+
*
|
|
1494
|
+
* WARNING (TOCTOU): The existing row is fetched before the policy check.
|
|
1495
|
+
* In concurrent environments, the row could be modified between fetch and
|
|
1496
|
+
* check. For safety, call this method within a transaction. The underlying
|
|
1497
|
+
* MutationGuard.checkUpdate documents this in detail.
|
|
1458
1498
|
*/
|
|
1459
1499
|
async update(id, data) {
|
|
1460
1500
|
if (!originalUpdate) {
|
|
@@ -1507,7 +1547,12 @@ function rlsPlugin(options) {
|
|
|
1507
1547
|
return await originalUpdate(id, data);
|
|
1508
1548
|
},
|
|
1509
1549
|
/**
|
|
1510
|
-
* Wrapped delete with RLS check
|
|
1550
|
+
* Wrapped delete with RLS check.
|
|
1551
|
+
*
|
|
1552
|
+
* WARNING (TOCTOU): The existing row is fetched before the policy check.
|
|
1553
|
+
* In concurrent environments, the row could be modified between fetch and
|
|
1554
|
+
* check. For safety, call this method within a transaction. The underlying
|
|
1555
|
+
* MutationGuard.checkDelete documents this in detail.
|
|
1511
1556
|
*/
|
|
1512
1557
|
async delete(id) {
|
|
1513
1558
|
if (!originalDelete) {
|