snipe-auth-rbac 0.3.0 → 0.4.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/dist/admin/index.cjs +201 -11
- package/dist/admin/index.cjs.map +1 -1
- package/dist/admin/index.d.cts +112 -3
- package/dist/admin/index.d.ts +112 -3
- package/dist/admin/index.js +200 -12
- package/dist/admin/index.js.map +1 -1
- package/dist/{chunk-NRDW233A.js → chunk-5UAIIOKT.js} +65 -1
- package/dist/chunk-5UAIIOKT.js.map +1 -0
- package/dist/{chunk-C76JHCKM.js → chunk-XHPBUCFN.js} +33 -1
- package/dist/chunk-XHPBUCFN.js.map +1 -0
- package/dist/index-CJqb5nY5.d.cts +191 -0
- package/dist/index-nfrns9Ye.d.ts +191 -0
- package/dist/index.cjs +42 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -118
- package/dist/index.d.ts +3 -118
- package/dist/index.js +4 -2
- package/dist/react/index.cjs +106 -11
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +34 -59
- package/dist/react/index.d.ts +34 -59
- package/dist/react/index.js +14 -13
- package/dist/react/index.js.map +1 -1
- package/dist/types-Oj9yfWvz.d.cts +132 -0
- package/dist/types-Oj9yfWvz.d.ts +132 -0
- package/package.json +1 -1
- package/sql/0001_initial.sql +137 -2
- package/sql/0002_seed_defaults.sql +29 -19
- package/dist/chunk-C76JHCKM.js.map +0 -1
- package/dist/chunk-NRDW233A.js.map +0 -1
- package/dist/types-DxvFudPF.d.cts +0 -69
- package/dist/types-DxvFudPF.d.ts +0 -69
package/dist/admin/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as ResourceScope, F as FrontendConfig, a as ResourceDescriptor
|
|
1
|
+
import { A as Action, R as ResourceScope, F as FrontendConfig, a as ResourceDescriptor } from '../types-Oj9yfWvz.cjs';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -27,6 +27,32 @@ interface AdminRolePermission {
|
|
|
27
27
|
can_write: boolean;
|
|
28
28
|
can_update: boolean;
|
|
29
29
|
can_delete: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `<action>_granted_via` columns from `rbac.role_permissions`.
|
|
32
|
+
* `null` = direct admin grant. Non-null = name of the parent
|
|
33
|
+
* resource whose `dependsOn` edge implied this cell. Used to
|
|
34
|
+
* render the "Implied by …" badge in the matrix UI.
|
|
35
|
+
*
|
|
36
|
+
* Available since 0.4.0. Pre-0.4.0 SQL omits these columns; the
|
|
37
|
+
* transport tolerates absent fields by treating them as null.
|
|
38
|
+
*/
|
|
39
|
+
read_granted_via?: string | null;
|
|
40
|
+
write_granted_via?: string | null;
|
|
41
|
+
update_granted_via?: string | null;
|
|
42
|
+
delete_granted_via?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* One materialised cascade edge. Returned by
|
|
46
|
+
* `AdminTransport.listResourceDependencies()` and consumed by the
|
|
47
|
+
* matrix UI when an admin toggles a cell on — used to decide which
|
|
48
|
+
* implied rows to write alongside.
|
|
49
|
+
*
|
|
50
|
+
* Available since 0.4.0.
|
|
51
|
+
*/
|
|
52
|
+
interface AdminResourceDependency {
|
|
53
|
+
parent_resource: string;
|
|
54
|
+
child_resource: string;
|
|
55
|
+
action: Action;
|
|
30
56
|
}
|
|
31
57
|
interface AdminCompany {
|
|
32
58
|
id: string;
|
|
@@ -81,7 +107,43 @@ interface AdminTransport {
|
|
|
81
107
|
resource: string;
|
|
82
108
|
action: Action;
|
|
83
109
|
value: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* 0.4.0+. When this write is the result of a dependsOn cascade,
|
|
112
|
+
* pass the parent resource name so the row's
|
|
113
|
+
* `<action>_granted_via` column is recorded. Direct admin
|
|
114
|
+
* clicks should pass `null` (or omit) to set the column to NULL
|
|
115
|
+
* — which is how `canAccessSection` distinguishes direct grants
|
|
116
|
+
* from implied ones.
|
|
117
|
+
*/
|
|
118
|
+
grantedVia?: string | null;
|
|
84
119
|
}): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* 0.4.0+. Batch variant for the dependsOn cascade — apply the
|
|
122
|
+
* parent's toggle AND every implied child in a single round-trip.
|
|
123
|
+
* Default implementation falls back to calling
|
|
124
|
+
* `setRolePermissionCell` once per write; transports that can
|
|
125
|
+
* batch (e.g. via a single `UPSERT … VALUES (…), (…), (…)`) should
|
|
126
|
+
* override.
|
|
127
|
+
*/
|
|
128
|
+
batchSetRolePermissionCells(writes: ReadonlyArray<{
|
|
129
|
+
role_id: string;
|
|
130
|
+
resource: string;
|
|
131
|
+
action: Action;
|
|
132
|
+
value: boolean;
|
|
133
|
+
grantedVia?: string | null;
|
|
134
|
+
}>): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* 0.4.0+. Push the `dependsOn` edges from the host's registry
|
|
137
|
+
* into `rbac.resource_dependencies`. Normally chained off
|
|
138
|
+
* `syncResources()` on app boot.
|
|
139
|
+
*/
|
|
140
|
+
syncResourceDependencies(edges: ReadonlyArray<AdminResourceDependency>): Promise<number>;
|
|
141
|
+
/**
|
|
142
|
+
* 0.4.0+. Read the materialised dependency graph back. The matrix
|
|
143
|
+
* UI hook calls this once at mount to know which children to
|
|
144
|
+
* cascade on a parent toggle.
|
|
145
|
+
*/
|
|
146
|
+
listResourceDependencies(): Promise<AdminResourceDependency[]>;
|
|
85
147
|
/**
|
|
86
148
|
* Materialise `rbac.role_permissions` rows from a template role's
|
|
87
149
|
* `default_permissions` JSONB pattern. Calls the SQL function
|
|
@@ -187,6 +249,13 @@ interface SupabaseAdminClientOptions {
|
|
|
187
249
|
/** Where the invitee should land after setting their password. */
|
|
188
250
|
inviteRedirectUrl?: string;
|
|
189
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Pull `dependsOn` edges out of a registry array and flatten them
|
|
254
|
+
* into one row per (parent, child, action). Shared helper used by
|
|
255
|
+
* `syncResources` and by adopters who want to sync dependencies
|
|
256
|
+
* manually.
|
|
257
|
+
*/
|
|
258
|
+
declare function extractResourceDependencies(resources: ReadonlyArray<ResourceDescriptor>): AdminResourceDependency[];
|
|
190
259
|
declare function createSupabaseAdminClient(opts: SupabaseAdminClientOptions): AdminTransport;
|
|
191
260
|
|
|
192
261
|
interface AdminTransportProviderProps {
|
|
@@ -251,6 +320,7 @@ declare function useSetRolePermissionCell(): {
|
|
|
251
320
|
resource: string;
|
|
252
321
|
action: Action;
|
|
253
322
|
value: boolean;
|
|
323
|
+
grantedVia?: string | null;
|
|
254
324
|
}) => Promise<void>;
|
|
255
325
|
};
|
|
256
326
|
declare function useApplyTemplateDefaults(): {
|
|
@@ -261,6 +331,17 @@ declare function useApplyTemplateDefaults(): {
|
|
|
261
331
|
only_missing?: boolean;
|
|
262
332
|
}) => Promise<number>;
|
|
263
333
|
};
|
|
334
|
+
/**
|
|
335
|
+
* 0.4.0+. Materialised dependency edges. Loaded once per admin
|
|
336
|
+
* session — the underlying table mutates only on app boot (via
|
|
337
|
+
* `syncResources` → `syncResourceDependencies`).
|
|
338
|
+
*/
|
|
339
|
+
declare function useAdminResourceDependencies(): {
|
|
340
|
+
refresh: () => Promise<void>;
|
|
341
|
+
data: AdminResourceDependency[] | null;
|
|
342
|
+
isLoading: boolean;
|
|
343
|
+
error: Error | null;
|
|
344
|
+
};
|
|
264
345
|
declare function useCreateCompany(): {
|
|
265
346
|
isPending: boolean;
|
|
266
347
|
error: Error | null;
|
|
@@ -286,8 +367,20 @@ interface RolePermissionGrid {
|
|
|
286
367
|
[A in Action]: boolean;
|
|
287
368
|
};
|
|
288
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* 0.4.0+. Per-cell origin tracking — `null` means a direct admin
|
|
372
|
+
* grant, a string is the name of the parent resource whose
|
|
373
|
+
* `dependsOn` edge implied the row. Used by the matrix UI to render
|
|
374
|
+
* the "Implied by …" badge.
|
|
375
|
+
*/
|
|
376
|
+
interface RolePermissionOriginGrid {
|
|
377
|
+
[resource: string]: {
|
|
378
|
+
[A in Action]: string | null;
|
|
379
|
+
};
|
|
380
|
+
}
|
|
289
381
|
declare function useRolePermissionGrid(roleId: string | null): {
|
|
290
382
|
grid: RolePermissionGrid;
|
|
383
|
+
originGrid: RolePermissionOriginGrid;
|
|
291
384
|
isLoading: boolean;
|
|
292
385
|
error: Error | null;
|
|
293
386
|
refresh: () => Promise<void>;
|
|
@@ -305,7 +398,23 @@ interface MatrixRenderArgs {
|
|
|
305
398
|
groups: MatrixGroup[];
|
|
306
399
|
/** Read a single cell from the current grid. */
|
|
307
400
|
isCellEnabled: (resource: string, action: Action) => boolean;
|
|
308
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* Origin of a single cell — `'direct'` for a direct admin grant
|
|
403
|
+
* (or off), or the name of the parent resource whose `dependsOn`
|
|
404
|
+
* edge implied the row. The consumer renders an "Implied by …"
|
|
405
|
+
* badge whenever this returns a non-`'direct'` value.
|
|
406
|
+
*
|
|
407
|
+
* Available since 0.4.0. With pre-0.4.0 SQL (no granted_via
|
|
408
|
+
* columns) this always returns `'direct'`.
|
|
409
|
+
*/
|
|
410
|
+
cellOrigin: (resource: string, action: Action) => "direct" | string;
|
|
411
|
+
/**
|
|
412
|
+
* Write a single cell. Optimistic in the local cache + writes
|
|
413
|
+
* through. On toggle-on, also writes implied rows for every
|
|
414
|
+
* `dependsOn` edge whose `actions` include the toggled action —
|
|
415
|
+
* those rows carry the parent's name in
|
|
416
|
+
* `<action>_granted_via`. Toggle-off never cascades.
|
|
417
|
+
*/
|
|
309
418
|
setCell: (resource: string, action: Action, value: boolean) => Promise<void>;
|
|
310
419
|
isLoading: boolean;
|
|
311
420
|
isUpdating: boolean;
|
|
@@ -374,4 +483,4 @@ interface InviteMemberFormProps {
|
|
|
374
483
|
}
|
|
375
484
|
declare function InviteMemberForm(props: InviteMemberFormProps): react_jsx_runtime.JSX.Element;
|
|
376
485
|
|
|
377
|
-
export { type AdminCompany, type AdminMember, type AdminRole, type AdminRolePermission, type AdminTransport, AdminTransportProvider, type AdminTransportProviderProps, InviteMemberForm, type InviteMemberFormProps, type InviteMemberFormRenderArgs, type MatrixGroup, type MatrixRenderArgs, PermissionsMatrix, type PermissionsMatrixProps, type RolePermissionGrid, RolesList, type RolesListProps, type RolesListRenderArgs, type SupabaseAdminClientOptions, createSupabaseAdminClient, useAdminCompanies, useAdminCompanyMembers, useAdminRolePermissions, useAdminRoles, useApplyTemplateDefaults, useCreateCompany, useCreateRole, useDeleteRole, useInviteCompanyMember, useRolePermissionGrid, useSetRolePermissionCell, useUpdateRole };
|
|
486
|
+
export { type AdminCompany, type AdminMember, type AdminResourceDependency, type AdminRole, type AdminRolePermission, type AdminTransport, AdminTransportProvider, type AdminTransportProviderProps, InviteMemberForm, type InviteMemberFormProps, type InviteMemberFormRenderArgs, type MatrixGroup, type MatrixRenderArgs, PermissionsMatrix, type PermissionsMatrixProps, type RolePermissionGrid, type RolePermissionOriginGrid, RolesList, type RolesListProps, type RolesListRenderArgs, type SupabaseAdminClientOptions, createSupabaseAdminClient, extractResourceDependencies, useAdminCompanies, useAdminCompanyMembers, useAdminResourceDependencies, useAdminRolePermissions, useAdminRoles, useApplyTemplateDefaults, useCreateCompany, useCreateRole, useDeleteRole, useInviteCompanyMember, useRolePermissionGrid, useSetRolePermissionCell, useUpdateRole };
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as ResourceScope, F as FrontendConfig, a as ResourceDescriptor
|
|
1
|
+
import { A as Action, R as ResourceScope, F as FrontendConfig, a as ResourceDescriptor } from '../types-Oj9yfWvz.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -27,6 +27,32 @@ interface AdminRolePermission {
|
|
|
27
27
|
can_write: boolean;
|
|
28
28
|
can_update: boolean;
|
|
29
29
|
can_delete: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `<action>_granted_via` columns from `rbac.role_permissions`.
|
|
32
|
+
* `null` = direct admin grant. Non-null = name of the parent
|
|
33
|
+
* resource whose `dependsOn` edge implied this cell. Used to
|
|
34
|
+
* render the "Implied by …" badge in the matrix UI.
|
|
35
|
+
*
|
|
36
|
+
* Available since 0.4.0. Pre-0.4.0 SQL omits these columns; the
|
|
37
|
+
* transport tolerates absent fields by treating them as null.
|
|
38
|
+
*/
|
|
39
|
+
read_granted_via?: string | null;
|
|
40
|
+
write_granted_via?: string | null;
|
|
41
|
+
update_granted_via?: string | null;
|
|
42
|
+
delete_granted_via?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* One materialised cascade edge. Returned by
|
|
46
|
+
* `AdminTransport.listResourceDependencies()` and consumed by the
|
|
47
|
+
* matrix UI when an admin toggles a cell on — used to decide which
|
|
48
|
+
* implied rows to write alongside.
|
|
49
|
+
*
|
|
50
|
+
* Available since 0.4.0.
|
|
51
|
+
*/
|
|
52
|
+
interface AdminResourceDependency {
|
|
53
|
+
parent_resource: string;
|
|
54
|
+
child_resource: string;
|
|
55
|
+
action: Action;
|
|
30
56
|
}
|
|
31
57
|
interface AdminCompany {
|
|
32
58
|
id: string;
|
|
@@ -81,7 +107,43 @@ interface AdminTransport {
|
|
|
81
107
|
resource: string;
|
|
82
108
|
action: Action;
|
|
83
109
|
value: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* 0.4.0+. When this write is the result of a dependsOn cascade,
|
|
112
|
+
* pass the parent resource name so the row's
|
|
113
|
+
* `<action>_granted_via` column is recorded. Direct admin
|
|
114
|
+
* clicks should pass `null` (or omit) to set the column to NULL
|
|
115
|
+
* — which is how `canAccessSection` distinguishes direct grants
|
|
116
|
+
* from implied ones.
|
|
117
|
+
*/
|
|
118
|
+
grantedVia?: string | null;
|
|
84
119
|
}): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* 0.4.0+. Batch variant for the dependsOn cascade — apply the
|
|
122
|
+
* parent's toggle AND every implied child in a single round-trip.
|
|
123
|
+
* Default implementation falls back to calling
|
|
124
|
+
* `setRolePermissionCell` once per write; transports that can
|
|
125
|
+
* batch (e.g. via a single `UPSERT … VALUES (…), (…), (…)`) should
|
|
126
|
+
* override.
|
|
127
|
+
*/
|
|
128
|
+
batchSetRolePermissionCells(writes: ReadonlyArray<{
|
|
129
|
+
role_id: string;
|
|
130
|
+
resource: string;
|
|
131
|
+
action: Action;
|
|
132
|
+
value: boolean;
|
|
133
|
+
grantedVia?: string | null;
|
|
134
|
+
}>): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* 0.4.0+. Push the `dependsOn` edges from the host's registry
|
|
137
|
+
* into `rbac.resource_dependencies`. Normally chained off
|
|
138
|
+
* `syncResources()` on app boot.
|
|
139
|
+
*/
|
|
140
|
+
syncResourceDependencies(edges: ReadonlyArray<AdminResourceDependency>): Promise<number>;
|
|
141
|
+
/**
|
|
142
|
+
* 0.4.0+. Read the materialised dependency graph back. The matrix
|
|
143
|
+
* UI hook calls this once at mount to know which children to
|
|
144
|
+
* cascade on a parent toggle.
|
|
145
|
+
*/
|
|
146
|
+
listResourceDependencies(): Promise<AdminResourceDependency[]>;
|
|
85
147
|
/**
|
|
86
148
|
* Materialise `rbac.role_permissions` rows from a template role's
|
|
87
149
|
* `default_permissions` JSONB pattern. Calls the SQL function
|
|
@@ -187,6 +249,13 @@ interface SupabaseAdminClientOptions {
|
|
|
187
249
|
/** Where the invitee should land after setting their password. */
|
|
188
250
|
inviteRedirectUrl?: string;
|
|
189
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Pull `dependsOn` edges out of a registry array and flatten them
|
|
254
|
+
* into one row per (parent, child, action). Shared helper used by
|
|
255
|
+
* `syncResources` and by adopters who want to sync dependencies
|
|
256
|
+
* manually.
|
|
257
|
+
*/
|
|
258
|
+
declare function extractResourceDependencies(resources: ReadonlyArray<ResourceDescriptor>): AdminResourceDependency[];
|
|
190
259
|
declare function createSupabaseAdminClient(opts: SupabaseAdminClientOptions): AdminTransport;
|
|
191
260
|
|
|
192
261
|
interface AdminTransportProviderProps {
|
|
@@ -251,6 +320,7 @@ declare function useSetRolePermissionCell(): {
|
|
|
251
320
|
resource: string;
|
|
252
321
|
action: Action;
|
|
253
322
|
value: boolean;
|
|
323
|
+
grantedVia?: string | null;
|
|
254
324
|
}) => Promise<void>;
|
|
255
325
|
};
|
|
256
326
|
declare function useApplyTemplateDefaults(): {
|
|
@@ -261,6 +331,17 @@ declare function useApplyTemplateDefaults(): {
|
|
|
261
331
|
only_missing?: boolean;
|
|
262
332
|
}) => Promise<number>;
|
|
263
333
|
};
|
|
334
|
+
/**
|
|
335
|
+
* 0.4.0+. Materialised dependency edges. Loaded once per admin
|
|
336
|
+
* session — the underlying table mutates only on app boot (via
|
|
337
|
+
* `syncResources` → `syncResourceDependencies`).
|
|
338
|
+
*/
|
|
339
|
+
declare function useAdminResourceDependencies(): {
|
|
340
|
+
refresh: () => Promise<void>;
|
|
341
|
+
data: AdminResourceDependency[] | null;
|
|
342
|
+
isLoading: boolean;
|
|
343
|
+
error: Error | null;
|
|
344
|
+
};
|
|
264
345
|
declare function useCreateCompany(): {
|
|
265
346
|
isPending: boolean;
|
|
266
347
|
error: Error | null;
|
|
@@ -286,8 +367,20 @@ interface RolePermissionGrid {
|
|
|
286
367
|
[A in Action]: boolean;
|
|
287
368
|
};
|
|
288
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* 0.4.0+. Per-cell origin tracking — `null` means a direct admin
|
|
372
|
+
* grant, a string is the name of the parent resource whose
|
|
373
|
+
* `dependsOn` edge implied the row. Used by the matrix UI to render
|
|
374
|
+
* the "Implied by …" badge.
|
|
375
|
+
*/
|
|
376
|
+
interface RolePermissionOriginGrid {
|
|
377
|
+
[resource: string]: {
|
|
378
|
+
[A in Action]: string | null;
|
|
379
|
+
};
|
|
380
|
+
}
|
|
289
381
|
declare function useRolePermissionGrid(roleId: string | null): {
|
|
290
382
|
grid: RolePermissionGrid;
|
|
383
|
+
originGrid: RolePermissionOriginGrid;
|
|
291
384
|
isLoading: boolean;
|
|
292
385
|
error: Error | null;
|
|
293
386
|
refresh: () => Promise<void>;
|
|
@@ -305,7 +398,23 @@ interface MatrixRenderArgs {
|
|
|
305
398
|
groups: MatrixGroup[];
|
|
306
399
|
/** Read a single cell from the current grid. */
|
|
307
400
|
isCellEnabled: (resource: string, action: Action) => boolean;
|
|
308
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* Origin of a single cell — `'direct'` for a direct admin grant
|
|
403
|
+
* (or off), or the name of the parent resource whose `dependsOn`
|
|
404
|
+
* edge implied the row. The consumer renders an "Implied by …"
|
|
405
|
+
* badge whenever this returns a non-`'direct'` value.
|
|
406
|
+
*
|
|
407
|
+
* Available since 0.4.0. With pre-0.4.0 SQL (no granted_via
|
|
408
|
+
* columns) this always returns `'direct'`.
|
|
409
|
+
*/
|
|
410
|
+
cellOrigin: (resource: string, action: Action) => "direct" | string;
|
|
411
|
+
/**
|
|
412
|
+
* Write a single cell. Optimistic in the local cache + writes
|
|
413
|
+
* through. On toggle-on, also writes implied rows for every
|
|
414
|
+
* `dependsOn` edge whose `actions` include the toggled action —
|
|
415
|
+
* those rows carry the parent's name in
|
|
416
|
+
* `<action>_granted_via`. Toggle-off never cascades.
|
|
417
|
+
*/
|
|
309
418
|
setCell: (resource: string, action: Action, value: boolean) => Promise<void>;
|
|
310
419
|
isLoading: boolean;
|
|
311
420
|
isUpdating: boolean;
|
|
@@ -374,4 +483,4 @@ interface InviteMemberFormProps {
|
|
|
374
483
|
}
|
|
375
484
|
declare function InviteMemberForm(props: InviteMemberFormProps): react_jsx_runtime.JSX.Element;
|
|
376
485
|
|
|
377
|
-
export { type AdminCompany, type AdminMember, type AdminRole, type AdminRolePermission, type AdminTransport, AdminTransportProvider, type AdminTransportProviderProps, InviteMemberForm, type InviteMemberFormProps, type InviteMemberFormRenderArgs, type MatrixGroup, type MatrixRenderArgs, PermissionsMatrix, type PermissionsMatrixProps, type RolePermissionGrid, RolesList, type RolesListProps, type RolesListRenderArgs, type SupabaseAdminClientOptions, createSupabaseAdminClient, useAdminCompanies, useAdminCompanyMembers, useAdminRolePermissions, useAdminRoles, useApplyTemplateDefaults, useCreateCompany, useCreateRole, useDeleteRole, useInviteCompanyMember, useRolePermissionGrid, useSetRolePermissionCell, useUpdateRole };
|
|
486
|
+
export { type AdminCompany, type AdminMember, type AdminResourceDependency, type AdminRole, type AdminRolePermission, type AdminTransport, AdminTransportProvider, type AdminTransportProviderProps, InviteMemberForm, type InviteMemberFormProps, type InviteMemberFormRenderArgs, type MatrixGroup, type MatrixRenderArgs, PermissionsMatrix, type PermissionsMatrixProps, type RolePermissionGrid, type RolePermissionOriginGrid, RolesList, type RolesListProps, type RolesListRenderArgs, type SupabaseAdminClientOptions, createSupabaseAdminClient, extractResourceDependencies, useAdminCompanies, useAdminCompanyMembers, useAdminResourceDependencies, useAdminRolePermissions, useAdminRoles, useApplyTemplateDefaults, useCreateCompany, useCreateRole, useDeleteRole, useInviteCompanyMember, useRolePermissionGrid, useSetRolePermissionCell, useUpdateRole };
|
package/dist/admin/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
groupResources
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-XHPBUCFN.js";
|
|
4
4
|
|
|
5
5
|
// src/admin/transport.ts
|
|
6
6
|
var ACTION_COLUMN = {
|
|
@@ -9,9 +9,46 @@ var ACTION_COLUMN = {
|
|
|
9
9
|
update: "can_update",
|
|
10
10
|
delete: "can_delete"
|
|
11
11
|
};
|
|
12
|
+
var GRANTED_VIA_COLUMN = {
|
|
13
|
+
read: "read_granted_via",
|
|
14
|
+
write: "write_granted_via",
|
|
15
|
+
update: "update_granted_via",
|
|
16
|
+
delete: "delete_granted_via"
|
|
17
|
+
};
|
|
18
|
+
function extractResourceDependencies(resources) {
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const r of resources) {
|
|
21
|
+
for (const edge of r.dependsOn ?? []) {
|
|
22
|
+
const child = typeof edge === "string" ? edge : edge.resource;
|
|
23
|
+
const actions = typeof edge === "string" ? ["read"] : edge.actions ?? ["read"];
|
|
24
|
+
for (const action of actions) {
|
|
25
|
+
out.push({
|
|
26
|
+
parent_resource: r.resource,
|
|
27
|
+
child_resource: child,
|
|
28
|
+
action
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
12
35
|
function createSupabaseAdminClient(opts) {
|
|
13
36
|
const sb = opts.supabase;
|
|
14
37
|
const rbac = sb.schema("rbac");
|
|
38
|
+
const syncResourceDependencies = async (edges) => {
|
|
39
|
+
const payload = edges.map((e) => ({
|
|
40
|
+
parent_resource: e.parent_resource,
|
|
41
|
+
child_resource: e.child_resource,
|
|
42
|
+
action: e.action
|
|
43
|
+
}));
|
|
44
|
+
const { error } = await rbac.rpc("replace_resource_dependencies", {
|
|
45
|
+
p_edges: payload
|
|
46
|
+
});
|
|
47
|
+
if (error) {
|
|
48
|
+
throw new Error(`syncResourceDependencies: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
return edges.length;
|
|
51
|
+
};
|
|
15
52
|
return {
|
|
16
53
|
async syncResources(resources) {
|
|
17
54
|
if (resources.length === 0) {
|
|
@@ -28,6 +65,15 @@ function createSupabaseAdminClient(opts) {
|
|
|
28
65
|
if (error) {
|
|
29
66
|
throw new Error(`syncResources: ${error.message}`);
|
|
30
67
|
}
|
|
68
|
+
const edges = extractResourceDependencies(resources);
|
|
69
|
+
try {
|
|
70
|
+
await syncResourceDependencies(edges);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof Error && /resource_dependencies/i.test(err.message) && /(does not exist|relation .* does not exist)/i.test(err.message)) {
|
|
73
|
+
} else {
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
31
77
|
return resources.length;
|
|
32
78
|
},
|
|
33
79
|
async listRoles({ scope, companyId, templatesOnly }) {
|
|
@@ -77,18 +123,68 @@ function createSupabaseAdminClient(opts) {
|
|
|
77
123
|
throw new Error(`deleteRole: ${error.message}`);
|
|
78
124
|
}
|
|
79
125
|
},
|
|
80
|
-
async setRolePermissionCell({ role_id, resource, action, value }) {
|
|
81
|
-
const
|
|
126
|
+
async setRolePermissionCell({ role_id, resource, action, value, grantedVia }) {
|
|
127
|
+
const actionCol = ACTION_COLUMN[action];
|
|
128
|
+
const originCol = GRANTED_VIA_COLUMN[action];
|
|
82
129
|
const row = {
|
|
83
130
|
role_id,
|
|
84
131
|
resource,
|
|
85
|
-
[
|
|
132
|
+
[actionCol]: value
|
|
86
133
|
};
|
|
134
|
+
if (grantedVia !== void 0) {
|
|
135
|
+
row[originCol] = value ? grantedVia : null;
|
|
136
|
+
}
|
|
87
137
|
const { error } = await rbac.from("role_permissions").upsert(row, { onConflict: "role_id,resource" });
|
|
88
138
|
if (error) {
|
|
139
|
+
if (grantedVia !== void 0 && /column .*granted_via.* does not exist/i.test(error.message)) {
|
|
140
|
+
const fallbackRow = {
|
|
141
|
+
role_id,
|
|
142
|
+
resource,
|
|
143
|
+
[actionCol]: value
|
|
144
|
+
};
|
|
145
|
+
const { error: retryErr } = await rbac.from("role_permissions").upsert(fallbackRow, { onConflict: "role_id,resource" });
|
|
146
|
+
if (retryErr) {
|
|
147
|
+
throw new Error(`setRolePermissionCell: ${retryErr.message}`);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
89
151
|
throw new Error(`setRolePermissionCell: ${error.message}`);
|
|
90
152
|
}
|
|
91
153
|
},
|
|
154
|
+
async batchSetRolePermissionCells(writes) {
|
|
155
|
+
if (writes.length === 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const w of writes) {
|
|
160
|
+
const key = `${w.role_id}::${w.resource}`;
|
|
161
|
+
const existing = byKey.get(key) ?? {
|
|
162
|
+
role_id: w.role_id,
|
|
163
|
+
resource: w.resource
|
|
164
|
+
};
|
|
165
|
+
existing[ACTION_COLUMN[w.action]] = w.value;
|
|
166
|
+
if (w.grantedVia !== void 0) {
|
|
167
|
+
existing[GRANTED_VIA_COLUMN[w.action]] = w.value ? w.grantedVia : null;
|
|
168
|
+
}
|
|
169
|
+
byKey.set(key, existing);
|
|
170
|
+
}
|
|
171
|
+
const payload = Array.from(byKey.values());
|
|
172
|
+
const { error } = await rbac.from("role_permissions").upsert(payload, { onConflict: "role_id,resource" });
|
|
173
|
+
if (error) {
|
|
174
|
+
throw new Error(`batchSetRolePermissionCells: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
syncResourceDependencies,
|
|
178
|
+
async listResourceDependencies() {
|
|
179
|
+
const { data, error } = await rbac.from("resource_dependencies").select("parent_resource, child_resource, action").order("parent_resource", { ascending: true });
|
|
180
|
+
if (error) {
|
|
181
|
+
if (/resource_dependencies/i.test(error.message) && /does not exist/i.test(error.message)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`listResourceDependencies: ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
return data ?? [];
|
|
187
|
+
},
|
|
92
188
|
async applyTemplateDefaults({ role_id, only_missing = true }) {
|
|
93
189
|
const { data, error } = await rbac.rpc("apply_template_defaults", {
|
|
94
190
|
p_role_id: role_id,
|
|
@@ -265,6 +361,13 @@ function useApplyTemplateDefaults() {
|
|
|
265
361
|
const transport = useAdminTransport();
|
|
266
362
|
return useMutation(transport.applyTemplateDefaults);
|
|
267
363
|
}
|
|
364
|
+
function useAdminResourceDependencies() {
|
|
365
|
+
const transport = useAdminTransport();
|
|
366
|
+
return useAsync(
|
|
367
|
+
() => transport.listResourceDependencies(),
|
|
368
|
+
[transport]
|
|
369
|
+
);
|
|
370
|
+
}
|
|
268
371
|
function useCreateCompany() {
|
|
269
372
|
const transport = useAdminTransport();
|
|
270
373
|
return useMutation(transport.createCompany);
|
|
@@ -275,7 +378,11 @@ function useInviteCompanyMember() {
|
|
|
275
378
|
}
|
|
276
379
|
function useRolePermissionGrid(roleId) {
|
|
277
380
|
const { data, isLoading, error, refresh } = useAdminRolePermissions(roleId);
|
|
381
|
+
const dependencies = useAdminResourceDependencies();
|
|
278
382
|
const setCell = useSetRolePermissionCell();
|
|
383
|
+
const transport = useAdminTransport();
|
|
384
|
+
const [isCascading, setCascading] = useState(false);
|
|
385
|
+
const [cascadeError, setCascadeError] = useState(null);
|
|
279
386
|
const grid = useMemo(() => {
|
|
280
387
|
const out = {};
|
|
281
388
|
for (const row of data ?? []) {
|
|
@@ -288,33 +395,107 @@ function useRolePermissionGrid(roleId) {
|
|
|
288
395
|
}
|
|
289
396
|
return out;
|
|
290
397
|
}, [data]);
|
|
398
|
+
const originGrid = useMemo(() => {
|
|
399
|
+
const out = {};
|
|
400
|
+
for (const row of data ?? []) {
|
|
401
|
+
out[row.resource] = {
|
|
402
|
+
read: row.read_granted_via ?? null,
|
|
403
|
+
write: row.write_granted_via ?? null,
|
|
404
|
+
update: row.update_granted_via ?? null,
|
|
405
|
+
delete: row.delete_granted_via ?? null
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}, [data]);
|
|
410
|
+
const edgesByParent = useMemo(() => {
|
|
411
|
+
const map = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const edge of dependencies.data ?? []) {
|
|
413
|
+
const list = map.get(edge.parent_resource) ?? [];
|
|
414
|
+
map.set(edge.parent_resource, [
|
|
415
|
+
...list,
|
|
416
|
+
{ child: edge.child_resource, action: edge.action }
|
|
417
|
+
]);
|
|
418
|
+
}
|
|
419
|
+
return map;
|
|
420
|
+
}, [dependencies.data]);
|
|
291
421
|
const updateCell = useCallback(
|
|
292
422
|
async (resource, action, value) => {
|
|
293
423
|
if (!roleId) {
|
|
294
424
|
return;
|
|
295
425
|
}
|
|
296
|
-
|
|
297
|
-
|
|
426
|
+
const writes = [
|
|
427
|
+
{ role_id: roleId, resource, action, value, grantedVia: null }
|
|
428
|
+
];
|
|
429
|
+
if (value) {
|
|
430
|
+
const edges = edgesByParent.get(resource) ?? [];
|
|
431
|
+
for (const edge of edges) {
|
|
432
|
+
if (edge.action !== action) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const childRow = (data ?? []).find((r) => r.resource === edge.child);
|
|
436
|
+
const childValue = childRow?.[ACTION_FIELD[action]] === true;
|
|
437
|
+
const childOrigin = childRow?.[ORIGIN_FIELD[action]] ?? null;
|
|
438
|
+
if (childValue && childOrigin == null) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
writes.push({
|
|
442
|
+
role_id: roleId,
|
|
443
|
+
resource: edge.child,
|
|
444
|
+
action,
|
|
445
|
+
value: true,
|
|
446
|
+
grantedVia: resource
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
setCascading(true);
|
|
451
|
+
setCascadeError(null);
|
|
452
|
+
try {
|
|
453
|
+
const [first, ...rest] = writes;
|
|
454
|
+
if (first && rest.length === 0) {
|
|
455
|
+
await setCell.mutate(first);
|
|
456
|
+
} else {
|
|
457
|
+
await transport.batchSetRolePermissionCells(writes);
|
|
458
|
+
}
|
|
459
|
+
void refresh();
|
|
460
|
+
} catch (e) {
|
|
461
|
+
setCascadeError(e instanceof Error ? e : new Error(String(e)));
|
|
462
|
+
throw e;
|
|
463
|
+
} finally {
|
|
464
|
+
setCascading(false);
|
|
465
|
+
}
|
|
298
466
|
},
|
|
299
|
-
[roleId, setCell, refresh]
|
|
467
|
+
[roleId, setCell, refresh, edgesByParent, data, transport]
|
|
300
468
|
);
|
|
301
469
|
return {
|
|
302
470
|
grid,
|
|
303
|
-
|
|
304
|
-
|
|
471
|
+
originGrid,
|
|
472
|
+
isLoading: isLoading || dependencies.isLoading,
|
|
473
|
+
error: error ?? dependencies.error,
|
|
305
474
|
refresh,
|
|
306
475
|
updateCell,
|
|
307
|
-
isUpdating: setCell.isPending,
|
|
308
|
-
updateError: setCell.error
|
|
476
|
+
isUpdating: setCell.isPending || isCascading,
|
|
477
|
+
updateError: setCell.error ?? cascadeError
|
|
309
478
|
};
|
|
310
479
|
}
|
|
480
|
+
var ACTION_FIELD = {
|
|
481
|
+
read: "can_read",
|
|
482
|
+
write: "can_write",
|
|
483
|
+
update: "can_update",
|
|
484
|
+
delete: "can_delete"
|
|
485
|
+
};
|
|
486
|
+
var ORIGIN_FIELD = {
|
|
487
|
+
read: "read_granted_via",
|
|
488
|
+
write: "write_granted_via",
|
|
489
|
+
update: "update_granted_via",
|
|
490
|
+
delete: "delete_granted_via"
|
|
491
|
+
};
|
|
311
492
|
|
|
312
493
|
// src/admin/PermissionsMatrix.tsx
|
|
313
494
|
import { useMemo as useMemo2 } from "react";
|
|
314
495
|
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
315
496
|
var ACTIONS = ["read", "write", "update", "delete"];
|
|
316
497
|
function PermissionsMatrix(props) {
|
|
317
|
-
const { grid, isLoading, error, updateCell, isUpdating } = useRolePermissionGrid(props.roleId);
|
|
498
|
+
const { grid, originGrid, isLoading, error, updateCell, isUpdating } = useRolePermissionGrid(props.roleId);
|
|
318
499
|
const groups = useMemo2(
|
|
319
500
|
() => groupResources(props.resources),
|
|
320
501
|
[props.resources]
|
|
@@ -322,12 +503,17 @@ function PermissionsMatrix(props) {
|
|
|
322
503
|
const isCellEnabled = (resource, action) => {
|
|
323
504
|
return grid[resource]?.[action] ?? false;
|
|
324
505
|
};
|
|
506
|
+
const cellOrigin = (resource, action) => {
|
|
507
|
+
const origin = originGrid[resource]?.[action];
|
|
508
|
+
return origin == null ? "direct" : origin;
|
|
509
|
+
};
|
|
325
510
|
const setCell = async (resource, action, value) => {
|
|
326
511
|
await updateCell(resource, action, value);
|
|
327
512
|
};
|
|
328
513
|
return /* @__PURE__ */ jsx2(Fragment, { children: props.children({
|
|
329
514
|
groups,
|
|
330
515
|
isCellEnabled,
|
|
516
|
+
cellOrigin,
|
|
331
517
|
setCell,
|
|
332
518
|
isLoading,
|
|
333
519
|
isUpdating,
|
|
@@ -461,8 +647,10 @@ export {
|
|
|
461
647
|
PermissionsMatrix,
|
|
462
648
|
RolesList,
|
|
463
649
|
createSupabaseAdminClient,
|
|
650
|
+
extractResourceDependencies,
|
|
464
651
|
useAdminCompanies,
|
|
465
652
|
useAdminCompanyMembers,
|
|
653
|
+
useAdminResourceDependencies,
|
|
466
654
|
useAdminRolePermissions,
|
|
467
655
|
useAdminRoles,
|
|
468
656
|
useApplyTemplateDefaults,
|