@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.
@@ -1,6 +1,14 @@
1
- import { AuthController, Entity, EntityCollection, User } from "@rebasepro/types";
2
- export declare function checkOperation<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, entity: Entity<M> | null, targetOperation: "select" | "insert" | "update" | "delete"): boolean;
3
- export declare function canReadCollection<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>): boolean;
4
- export declare function canEditEntity<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null): boolean;
5
- export declare function canCreateEntity<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null): boolean;
6
- export declare function canDeleteEntity<M extends Record<string, unknown>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null): boolean;
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.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.4.0",
45
- "@rebasepro/utils": "0.4.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
- (relationProperty as RelationProperty & { relation?: Relation }).relation = relation;
308
+ relationProperty.relation = relation;
309
309
  } else {
310
310
  console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
311
311
  }
@@ -1,6 +1,15 @@
1
- import { AuthController, CollectionWithRelations, Entity, EntityCollection, getDataSourceCapabilities, SecurityRule, User } from "@rebasepro/types";
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: AuthController<USER>, entity: Entity<M> | null): boolean {
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: AuthController<USER>, entity: Entity<M> | null): boolean {
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
- authController: AuthController<USER>,
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
- // In Postgres, policies ONLY apply if the user matching the targeted roles.
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; // APPLIES TO PUBLIC
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, authController, entity);
181
+ const passed = evaluateRule(rule, authContext, entity);
178
182
 
179
183
  if (mode === "restrictive" && !passed) {
180
184
  deniedByRestrictive = true;
181
- break; // Immediate deny
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
- authController: AuthController<USER>
206
+ authContext: AuthContext<USER>
203
207
  ): boolean {
204
- return checkOperation(collection, authController, null, "select");
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
- authController: AuthController<USER>,
214
+ authContext: AuthContext<USER>,
211
215
  path: string,
212
216
  entity: Entity<M> | null
213
217
  ): boolean {
214
- return checkOperation(collection, authController, entity, "update");
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
- authController: AuthController<USER>,
224
+ authContext: AuthContext<USER>,
221
225
  path: string,
222
226
  entity: Entity<M> | null
223
227
  ): boolean {
224
- return checkOperation(collection, authController, entity, "insert");
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
- authController: AuthController<USER>,
234
+ authContext: AuthContext<USER>,
231
235
  path: string,
232
236
  entity: Entity<M> | null
233
237
  ): boolean {
234
- return checkOperation(collection, authController, entity, "delete");
238
+ return checkOperation(collection, authContext, entity, "delete");
235
239
  }