@rebasepro/common 0.4.0 → 0.5.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 +73 -220
- package/dist/index.es.js +11 -11
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +11 -11
- package/dist/index.umd.js.map +1 -1
- package/dist/util/permissions.d.ts +14 -6
- package/package.json +3 -3
- package/src/collections/CollectionRegistry.ts +1 -1
- package/src/util/permissions.ts +25 -21
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Entity, EntityCollection, User } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal auth context for permission checking.
|
|
4
|
+
* Only requires the user object — avoids forcing callers to construct
|
|
5
|
+
* a full AuthController just to check permissions.
|
|
6
|
+
*/
|
|
7
|
+
export interface AuthContext<USER extends User = User> {
|
|
8
|
+
user: USER | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function checkOperation<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authContext: AuthContext<USER>, entity: Entity<M> | null, targetOperation: "select" | "insert" | "update" | "delete"): boolean;
|
|
11
|
+
export declare function canReadCollection<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authContext: AuthContext<USER>): boolean;
|
|
12
|
+
export declare function canEditEntity<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authContext: AuthContext<USER>, path: string, entity: Entity<M> | null): boolean;
|
|
13
|
+
export declare function canCreateEntity<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authContext: AuthContext<USER>, path: string, entity: Entity<M> | null): boolean;
|
|
14
|
+
export declare function canDeleteEntity<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authContext: AuthContext<USER>, path: string, entity: Entity<M> | null): boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/common",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"fast-equals": "6.0.0",
|
|
43
43
|
"json-logic-js": "^2.0.5",
|
|
44
|
-
"@rebasepro/types": "0.
|
|
45
|
-
"@rebasepro/utils": "0.
|
|
44
|
+
"@rebasepro/types": "0.5.0",
|
|
45
|
+
"@rebasepro/utils": "0.5.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@jest/globals": "^29.7.0",
|
|
@@ -305,7 +305,7 @@ export class CollectionRegistry {
|
|
|
305
305
|
const relation = relations.find(r => r.relationName === name);
|
|
306
306
|
if (relation) {
|
|
307
307
|
// we attach the resolved relation to the property
|
|
308
|
-
|
|
308
|
+
relationProperty.relation = relation;
|
|
309
309
|
} else {
|
|
310
310
|
console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
|
|
311
311
|
}
|
package/src/util/permissions.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionWithRelations, Entity, EntityCollection, getDataSourceCapabilities, SecurityRule, User } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal auth context for permission checking.
|
|
5
|
+
* Only requires the user object — avoids forcing callers to construct
|
|
6
|
+
* a full AuthController just to check permissions.
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthContext<USER extends User = User> {
|
|
9
|
+
user: USER | null;
|
|
10
|
+
}
|
|
2
11
|
|
|
3
|
-
function evaluateAST<USER extends User, M extends Record<string, unknown>>(sqlString: string, auth:
|
|
12
|
+
function evaluateAST<USER extends User, M extends Record<string, unknown>>(sqlString: string, auth: AuthContext<USER>, entity: Entity<M> | null): boolean {
|
|
4
13
|
// This is a client-side SQL evaluator used *only* for optimistic UI updates.
|
|
5
14
|
// It parses basic AND / OR statements to evaluate RLS without backend roundtrips.
|
|
6
15
|
if (!entity) return true;
|
|
@@ -111,7 +120,7 @@ function evaluateAST<USER extends User, M extends Record<string, unknown>>(sqlSt
|
|
|
111
120
|
return true; // Optimistic fallback for anything else
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
function evaluateRule<USER extends User, M extends Record<string, unknown>>(rule: SecurityRule, auth:
|
|
123
|
+
function evaluateRule<USER extends User, M extends Record<string, unknown>>(rule: SecurityRule, auth: AuthContext<USER>, entity: Entity<M> | null): boolean {
|
|
115
124
|
|
|
116
125
|
if (rule.access === "public") return true;
|
|
117
126
|
|
|
@@ -137,15 +146,12 @@ function evaluateRule<USER extends User, M extends Record<string, unknown>>(rule
|
|
|
137
146
|
|
|
138
147
|
export function checkOperation<M extends Record<string, unknown>, USER extends User>(
|
|
139
148
|
collection: EntityCollection<M>,
|
|
140
|
-
|
|
149
|
+
authContext: AuthContext<USER>,
|
|
141
150
|
entity: Entity<M> | null,
|
|
142
151
|
targetOperation: "select" | "insert" | "update" | "delete"
|
|
143
152
|
): boolean {
|
|
144
153
|
const securityRules = getDataSourceCapabilities(collection.driver).supportsRLS ? (collection as CollectionWithRelations).securityRules : undefined;
|
|
145
154
|
if (!securityRules || securityRules.length === 0) {
|
|
146
|
-
// According to our plan: Postgres RLS implicitly denies if enabled without rules.
|
|
147
|
-
// But for Rebase we default to true if securityRules is undefined,
|
|
148
|
-
// so as not to break everything without rules. Let's assume true for now.
|
|
149
155
|
return true;
|
|
150
156
|
}
|
|
151
157
|
|
|
@@ -158,15 +164,13 @@ export function checkOperation<M extends Record<string, unknown>, USER extends U
|
|
|
158
164
|
|
|
159
165
|
if (applicableRules.length === 0) return false;
|
|
160
166
|
|
|
161
|
-
|
|
162
|
-
const userRoleIds = authController.user?.roles ?? [];
|
|
167
|
+
const userRoleIds = authContext.user?.roles ?? [];
|
|
163
168
|
const userRoles = [...userRoleIds, "public"];
|
|
164
169
|
const roleApplicableRules = applicableRules.filter((rule: SecurityRule) => {
|
|
165
|
-
if (!rule.roles || rule.roles.length === 0) return true;
|
|
170
|
+
if (!rule.roles || rule.roles.length === 0) return true;
|
|
166
171
|
return rule.roles.some((r: string) => userRoles.includes(r));
|
|
167
172
|
});
|
|
168
173
|
|
|
169
|
-
// If no rules apply to this user's roles, the operation is implicitly denied.
|
|
170
174
|
if (roleApplicableRules.length === 0) return false;
|
|
171
175
|
|
|
172
176
|
let grantedByPermissive = false;
|
|
@@ -174,11 +178,11 @@ export function checkOperation<M extends Record<string, unknown>, USER extends U
|
|
|
174
178
|
|
|
175
179
|
for (const rule of roleApplicableRules) {
|
|
176
180
|
const mode = rule.mode || "permissive";
|
|
177
|
-
const passed = evaluateRule(rule,
|
|
181
|
+
const passed = evaluateRule(rule, authContext, entity);
|
|
178
182
|
|
|
179
183
|
if (mode === "restrictive" && !passed) {
|
|
180
184
|
deniedByRestrictive = true;
|
|
181
|
-
break;
|
|
185
|
+
break;
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
if (mode === "permissive" && passed) {
|
|
@@ -199,37 +203,37 @@ export function checkOperation<M extends Record<string, unknown>, USER extends U
|
|
|
199
203
|
export function canReadCollection<M extends Record<string, unknown>, USER extends User>
|
|
200
204
|
(
|
|
201
205
|
collection: EntityCollection<M>,
|
|
202
|
-
|
|
206
|
+
authContext: AuthContext<USER>
|
|
203
207
|
): boolean {
|
|
204
|
-
return checkOperation(collection,
|
|
208
|
+
return checkOperation(collection, authContext, null, "select");
|
|
205
209
|
}
|
|
206
210
|
|
|
207
211
|
export function canEditEntity<M extends Record<string, unknown>, USER extends User>
|
|
208
212
|
(
|
|
209
213
|
collection: EntityCollection<M>,
|
|
210
|
-
|
|
214
|
+
authContext: AuthContext<USER>,
|
|
211
215
|
path: string,
|
|
212
216
|
entity: Entity<M> | null
|
|
213
217
|
): boolean {
|
|
214
|
-
return checkOperation(collection,
|
|
218
|
+
return checkOperation(collection, authContext, entity, "update");
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
export function canCreateEntity<M extends Record<string, unknown>, USER extends User>
|
|
218
222
|
(
|
|
219
223
|
collection: EntityCollection<M>,
|
|
220
|
-
|
|
224
|
+
authContext: AuthContext<USER>,
|
|
221
225
|
path: string,
|
|
222
226
|
entity: Entity<M> | null
|
|
223
227
|
): boolean {
|
|
224
|
-
return checkOperation(collection,
|
|
228
|
+
return checkOperation(collection, authContext, entity, "insert");
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
export function canDeleteEntity<M extends Record<string, unknown>, USER extends User>
|
|
228
232
|
(
|
|
229
233
|
collection: EntityCollection<M>,
|
|
230
|
-
|
|
234
|
+
authContext: AuthContext<USER>,
|
|
231
235
|
path: string,
|
|
232
236
|
entity: Entity<M> | null
|
|
233
237
|
): boolean {
|
|
234
|
-
return checkOperation(collection,
|
|
238
|
+
return checkOperation(collection, authContext, entity, "delete");
|
|
235
239
|
}
|