@invect/rbac 0.0.1 → 0.0.3

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.
Files changed (35) hide show
  1. package/README.md +39 -77
  2. package/dist/backend/index.cjs +72 -40
  3. package/dist/backend/index.cjs.map +1 -1
  4. package/dist/backend/index.d.cts +49 -0
  5. package/dist/backend/index.d.cts.map +1 -0
  6. package/dist/backend/index.d.mts +49 -0
  7. package/dist/backend/index.d.mts.map +1 -0
  8. package/dist/backend/index.d.ts +1 -1
  9. package/dist/backend/index.d.ts.map +1 -1
  10. package/dist/backend/index.mjs +72 -40
  11. package/dist/backend/index.mjs.map +1 -1
  12. package/dist/backend/plugin.d.ts +12 -14
  13. package/dist/backend/plugin.d.ts.map +1 -1
  14. package/dist/frontend/components/TeamsPage.d.ts.map +1 -1
  15. package/dist/frontend/components/access-control/ScopeDetailPanel.d.ts.map +1 -1
  16. package/dist/frontend/components/access-control/index.d.ts +0 -1
  17. package/dist/frontend/components/access-control/index.d.ts.map +1 -1
  18. package/dist/frontend/index.cjs +61 -61
  19. package/dist/frontend/index.cjs.map +1 -1
  20. package/dist/frontend/index.d.cts +227 -0
  21. package/dist/frontend/index.d.cts.map +1 -0
  22. package/dist/frontend/index.d.mts +227 -0
  23. package/dist/frontend/index.d.mts.map +1 -0
  24. package/dist/frontend/index.d.ts +2 -2
  25. package/dist/frontend/index.d.ts.map +1 -1
  26. package/dist/frontend/index.mjs +7 -7
  27. package/dist/frontend/index.mjs.map +1 -1
  28. package/dist/frontend/types.d.ts +1 -1
  29. package/dist/shared/types.d.cts +2 -0
  30. package/dist/shared/types.d.mts +2 -0
  31. package/dist/types-D4DI2gyU.d.cts +175 -0
  32. package/dist/types-D4DI2gyU.d.cts.map +1 -0
  33. package/dist/types-DxJoguYy.d.mts +175 -0
  34. package/dist/types-DxJoguYy.d.mts.map +1 -0
  35. package/package.json +44 -46
@@ -8,8 +8,8 @@
8
8
  * ```ts
9
9
  * import { resolveTeamIds } from '@invect/rbac/backend';
10
10
  *
11
- * betterAuthPlugin({
12
- * auth,
11
+ * auth({
12
+ * auth: betterAuthInstance,
13
13
  * mapUser: async (user, session) => ({
14
14
  * id: user.id,
15
15
  * name: user.name ?? undefined,
@@ -197,6 +197,58 @@ async function listAllDirectFlowAccess(db, flowId) {
197
197
  const now = Date.now();
198
198
  return rows.map(normalizeFlowAccessRecord).filter((record) => !record.expiresAt || new Date(record.expiresAt).getTime() > now);
199
199
  }
200
+ async function grantDirectFlowAccess(db, input) {
201
+ const now = (/* @__PURE__ */ new Date()).toISOString();
202
+ const existingRows = await db.query("SELECT id FROM flow_access WHERE flow_id = ? AND user_id IS ? AND team_id IS ?", [
203
+ input.flowId,
204
+ input.userId ?? null,
205
+ input.teamId ?? null
206
+ ]);
207
+ if (existingRows.length > 0) {
208
+ const existingId = String(existingRows[0].id);
209
+ await db.execute("UPDATE flow_access SET permission = ?, granted_by = ?, granted_at = ?, expires_at = ? WHERE id = ?", [
210
+ input.permission,
211
+ input.grantedBy ?? null,
212
+ now,
213
+ input.expiresAt ?? null,
214
+ existingId
215
+ ]);
216
+ return {
217
+ id: existingId,
218
+ flowId: input.flowId,
219
+ userId: input.userId ?? null,
220
+ teamId: input.teamId ?? null,
221
+ permission: input.permission,
222
+ grantedBy: input.grantedBy ?? null,
223
+ grantedAt: now,
224
+ expiresAt: input.expiresAt ?? null
225
+ };
226
+ }
227
+ const id = crypto.randomUUID();
228
+ await db.execute("INSERT INTO flow_access (id, flow_id, user_id, team_id, permission, granted_by, granted_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
229
+ id,
230
+ input.flowId,
231
+ input.userId ?? null,
232
+ input.teamId ?? null,
233
+ input.permission,
234
+ input.grantedBy ?? null,
235
+ now,
236
+ input.expiresAt ?? null
237
+ ]);
238
+ return {
239
+ id,
240
+ flowId: input.flowId,
241
+ userId: input.userId ?? null,
242
+ teamId: input.teamId ?? null,
243
+ permission: input.permission,
244
+ grantedBy: input.grantedBy ?? null,
245
+ grantedAt: now,
246
+ expiresAt: input.expiresAt ?? null
247
+ };
248
+ }
249
+ async function revokeDirectFlowAccess(db, accessId) {
250
+ await db.execute("DELETE FROM flow_access WHERE id = ?", [accessId]);
251
+ }
200
252
  async function listAllEffectiveFlowAccessForPreview(db, flowId, overrideScopeId) {
201
253
  const direct = await listAllDirectFlowAccess(db, flowId);
202
254
  const scopeId = overrideScopeId === void 0 ? await getFlowScopeId(db, flowId) : overrideScopeId;
@@ -279,8 +331,17 @@ async function resolveAccessChangeNames(db, entries) {
279
331
  source: entry.source
280
332
  }));
281
333
  }
282
- function rbacPlugin(options = {}) {
283
- const { useFlowAccessTable = true, adminPermission = "flow:read", enableTeams = true } = options;
334
+ function rbac(options = {}) {
335
+ const { frontend, ...backendOptions } = options;
336
+ return {
337
+ id: "rbac",
338
+ name: "Role-Based Access Control",
339
+ backend: _rbacBackendPlugin(backendOptions),
340
+ frontend
341
+ };
342
+ }
343
+ function _rbacBackendPlugin(options = {}) {
344
+ const { adminPermission = "flow:read", enableTeams = true } = options;
284
345
  const teamsSchema = enableTeams ? {
285
346
  flows: { fields: { scope_id: {
286
347
  type: "string",
@@ -444,10 +505,10 @@ function rbacPlugin(options = {}) {
444
505
  "rbac_scope_access"
445
506
  ] : []
446
507
  ],
447
- setupInstructions: "The RBAC plugin requires better-auth tables (user, session). Make sure @invect/user-auth is configured, then run `npx invect generate` followed by `npx drizzle-kit push`.",
508
+ setupInstructions: "The RBAC plugin requires user-auth tables (user, session). Make sure @invect/user-auth is configured, then run `npx invect-cli generate` followed by `npx drizzle-kit push`.",
448
509
  init: async (ctx) => {
449
- if (!ctx.hasPlugin("better-auth")) ctx.logger.warn("RBAC plugin requires the @invect/user-auth plugin. RBAC will work with reduced functionality (no session resolution). Make sure betterAuthPlugin() is registered before rbacPlugin().");
450
- ctx.logger.info("RBAC plugin initialized", { useFlowAccessTable });
510
+ if (!ctx.hasPlugin("user-auth")) ctx.logger.warn("RBAC plugin requires the @invect/user-auth plugin. RBAC will work with reduced functionality (no session resolution). Make sure auth() is registered before rbac().");
511
+ ctx.logger.info("RBAC plugin initialized");
451
512
  },
452
513
  endpoints: [
453
514
  {
@@ -510,13 +571,6 @@ function rbacPlugin(options = {}) {
510
571
  status: 400,
511
572
  body: { error: "Missing flowId parameter" }
512
573
  };
513
- if (!ctx.core.isFlowAccessTableEnabled()) return {
514
- status: 501,
515
- body: {
516
- error: "Not Implemented",
517
- message: "Flow access table not enabled. Set auth.useFlowAccessTable: true in config."
518
- }
519
- };
520
574
  if (!ctx.identity) return {
521
575
  status: 401,
522
576
  body: {
@@ -533,7 +587,7 @@ function rbacPlugin(options = {}) {
533
587
  };
534
588
  return {
535
589
  status: 200,
536
- body: { access: await ctx.core.listFlowAccess(flowId) }
590
+ body: { access: await listAllDirectFlowAccess(ctx.database, flowId) }
537
591
  };
538
592
  }
539
593
  },
@@ -547,13 +601,6 @@ function rbacPlugin(options = {}) {
547
601
  status: 400,
548
602
  body: { error: "Missing flowId parameter" }
549
603
  };
550
- if (!ctx.core.isFlowAccessTableEnabled()) return {
551
- status: 501,
552
- body: {
553
- error: "Not Implemented",
554
- message: "Flow access table not enabled. Set auth.useFlowAccessTable: true in config."
555
- }
556
- };
557
604
  const { userId, teamId, permission, expiresAt } = ctx.body;
558
605
  if (!userId && !teamId) return {
559
606
  status: 400,
@@ -584,7 +631,7 @@ function rbacPlugin(options = {}) {
584
631
  };
585
632
  return {
586
633
  status: 201,
587
- body: await ctx.core.grantFlowAccess({
634
+ body: await grantDirectFlowAccess(ctx.database, {
588
635
  flowId,
589
636
  userId,
590
637
  teamId,
@@ -605,13 +652,6 @@ function rbacPlugin(options = {}) {
605
652
  status: 400,
606
653
  body: { error: "Missing flowId or accessId parameter" }
607
654
  };
608
- if (!ctx.core.isFlowAccessTableEnabled()) return {
609
- status: 501,
610
- body: {
611
- error: "Not Implemented",
612
- message: "Flow access table not enabled. Set auth.useFlowAccessTable: true in config."
613
- }
614
- };
615
655
  if (!ctx.identity) return {
616
656
  status: 401,
617
657
  body: {
@@ -626,7 +666,7 @@ function rbacPlugin(options = {}) {
626
666
  message: "Owner access is required to manage sharing"
627
667
  }
628
668
  };
629
- await ctx.core.revokeFlowAccess(accessId);
669
+ await revokeDirectFlowAccess(ctx.database, accessId);
630
670
  return {
631
671
  status: 204,
632
672
  body: null
@@ -638,13 +678,6 @@ function rbacPlugin(options = {}) {
638
678
  path: "/rbac/flows/accessible",
639
679
  isPublic: false,
640
680
  handler: async (ctx) => {
641
- if (!ctx.core.isFlowAccessTableEnabled()) return {
642
- status: 501,
643
- body: {
644
- error: "Not Implemented",
645
- message: "Flow access table not enabled. Set auth.useFlowAccessTable: true in config."
646
- }
647
- };
648
681
  const identity = ctx.identity;
649
682
  if (!identity) return {
650
683
  status: 401,
@@ -1323,7 +1356,6 @@ function rbacPlugin(options = {}) {
1323
1356
  } catch {}
1324
1357
  } : void 0,
1325
1358
  onAuthorize: async (context) => {
1326
- if (!useFlowAccessTable) return;
1327
1359
  const { identity, resource, action } = context;
1328
1360
  if (!identity || !resource?.id) return;
1329
1361
  if (!FLOW_RESOURCE_TYPES.has(resource.type)) return;
@@ -1358,6 +1390,6 @@ function rbacPlugin(options = {}) {
1358
1390
  };
1359
1391
  }
1360
1392
  //#endregion
1361
- export { rbacPlugin, resolveTeamIds };
1393
+ export { rbac, resolveTeamIds };
1362
1394
 
1363
1395
  //# sourceMappingURL=index.mjs.map