@cedarjs/tenancy 8.0.0-canary.3569 → 8.0.0-canary.3571
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 +8 -7
- package/dist/permissions.d.ts +1 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +11 -21
- package/dist/web/createTenancy.d.ts +1 -1
- package/package.json +12 -17
- package/dist/definePermissions.d.ts +0 -48
- package/dist/definePermissions.d.ts.map +0 -1
- package/dist/definePermissions.js +0 -20
package/README.md
CHANGED
|
@@ -85,14 +85,15 @@ organization: fields about an organization ask for a permission (below).
|
|
|
85
85
|
## Permissions
|
|
86
86
|
|
|
87
87
|
Roles in an organization are named bundles of permissions, defined in one
|
|
88
|
-
place
|
|
88
|
+
place with `definePermissions` from
|
|
89
|
+
`@cedarjs/permissions`. Fields, services and components check a permission in an
|
|
89
90
|
organization, never the membership's role, so a role can do exactly what its
|
|
90
91
|
list says. `hasRole` and `<PrivateSet roles>` check the user's own roles, in
|
|
91
92
|
`currentUser.roles`.
|
|
92
93
|
|
|
93
94
|
```ts
|
|
94
95
|
// packages/permissions/src/index.ts, shared by the api and web sides
|
|
95
|
-
import { definePermissions } from '@cedarjs/
|
|
96
|
+
import { definePermissions } from '@cedarjs/permissions'
|
|
96
97
|
|
|
97
98
|
const member = ['organization.read', 'projects.read'] as const
|
|
98
99
|
|
|
@@ -149,7 +150,7 @@ side. Pass the tenancy to the router and put organization routes in a
|
|
|
149
150
|
immutable organization id so switching organizations never mixes caches, around
|
|
150
151
|
the set's layouts and page. `useAuth().hasPermission()` checks what the user
|
|
151
152
|
may do in the organization. To guard routes by a permission of the user's
|
|
152
|
-
membership, give a `PrivateSet` `
|
|
153
|
+
membership, give a `PrivateSet` `permission` and `unauthorized`:
|
|
153
154
|
|
|
154
155
|
<!-- prettier-ignore -->
|
|
155
156
|
```tsx
|
|
@@ -159,7 +160,7 @@ import { tenancy } from 'src/tenancy'
|
|
|
159
160
|
<PrivateSet wrap={OrgLayout} unauthenticated="login">
|
|
160
161
|
<Route path="/org/{orgSlug}/projects" page={ProjectsPage} name="projects" />
|
|
161
162
|
</PrivateSet>
|
|
162
|
-
<PrivateSet wrap={OrgLayout} unauthenticated="login" unauthorized="projects"
|
|
163
|
+
<PrivateSet wrap={OrgLayout} unauthenticated="login" unauthorized="projects" permission="members.manage">
|
|
163
164
|
<Route path="/org/{orgSlug}/members" page={MembersPage} name="members" />
|
|
164
165
|
</PrivateSet>
|
|
165
166
|
</Router>
|
|
@@ -175,9 +176,9 @@ export const tenancy = createTenancy({
|
|
|
175
176
|
|
|
176
177
|
The router checks the permission in the organization that `tenancy.useOrg`
|
|
177
178
|
returns. The default `useOrg` finds it by the route's `orgSlug` param, and a
|
|
178
|
-
custom one can find it another way, such as from the host. A `PrivateSet` with
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
custom one can find it another way, such as from the host. A `PrivateSet` with
|
|
180
|
+
`permission` throws when `useOrg` finds no organization for the route; with the
|
|
181
|
+
default lookup, that's a route without `{orgSlug}`. A route
|
|
181
182
|
with `{orgSlug}` outside a `PrivateSet` is a public page with no organization
|
|
182
183
|
context. `useAuth().hasRole` and `<PrivateSet roles>` check
|
|
183
184
|
`currentUser.roles`, the user's own roles, as in an app without tenancy;
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type { PermissionDefinitions } from '
|
|
2
|
-
export { definePermissions } from './definePermissions.js';
|
|
3
|
-
export type { PermissionDefinitions, PermissionName, } from './definePermissions.js';
|
|
1
|
+
import type { PermissionDefinitions } from '@cedarjs/permissions';
|
|
4
2
|
/** What `permissionsOf` needs to know about a membership */
|
|
5
3
|
export interface MembershipForPermissions {
|
|
6
4
|
role: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAIjE,4DAA4D;AAC5D,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wEAAwE;AACxE,MAAM,MAAM,eAAe,CACzB,UAAU,EACV,UAAU,SAAS,MAAM,IACvB,UAAU,GAAG;IAAE,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAAA;AAE9C,MAAM,WAAW,WAAW,CAAC,UAAU,SAAS,MAAM;IACpD;;;;OAIG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAA;IAC9C;;;;OAIG;IACH,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAA;IAC/C;;;;;;;;OAQG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACnC;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,wBAAwB,GAAG,UAAU,EAAE,CAAA;IACjE;;;;OAIG;IACH,eAAe,CAAC,IAAI,SAAS;QAAE,WAAW,EAAE,wBAAwB,EAAE,CAAA;KAAE,EACtE,IAAI,EAAE,IAAI,GACT,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG;QAC7B,WAAW,EAAE,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,CAAA;KACxE,CAAA;IACD,eAAe,CAAC,IAAI,SAAS;QAAE,WAAW,EAAE,wBAAwB,EAAE,CAAA;KAAE,EACtE,IAAI,EAAE,IAAI,GAAG,IAAI,GAEf,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG;QAC3B,WAAW,EAAE,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,CAAA;KACxE,CAAC,GACF,IAAI,CAAA;CACT;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,UAAU,SAAS,MAAM,EAC/D,WAAW,EAAE,qBAAqB,CAAC,UAAU,CAAC,GAC7C,WAAW,CAAC,UAAU,CAAC,CAoFzB"}
|
package/dist/permissions.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { context } from "@cedarjs/context";
|
|
2
2
|
import { AuthenticationError, ForbiddenError } from "@cedarjs/graphql-server";
|
|
3
|
+
import {
|
|
4
|
+
assertPermission,
|
|
5
|
+
assertPermissionDefinitions,
|
|
6
|
+
coversRole,
|
|
7
|
+
permissionsOf as rolePermissions
|
|
8
|
+
} from "@cedarjs/permissions";
|
|
3
9
|
import { getCurrentOrg, isUserWithMemberships } from "./context.js";
|
|
4
|
-
import { assertPermissionDefinitions } from "./definePermissions.js";
|
|
5
|
-
import { definePermissions } from "./definePermissions.js";
|
|
6
10
|
function createPermissions(definitions) {
|
|
7
11
|
assertPermissionDefinitions(definitions);
|
|
8
|
-
const
|
|
9
|
-
if (!definitions.permissions.includes(permission)) {
|
|
10
|
-
throw new Error(
|
|
11
|
-
`Unknown permission "${permission}". Add it to \`permissions\` in your permission definitions before using it.`
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
const permissionsOf = (membership) => Object.hasOwn(definitions.roles, membership.role) ? [...definitions.roles[membership.role]] : [];
|
|
12
|
+
const permissionsOf = (membership) => rolePermissions(definitions, membership.role);
|
|
16
13
|
const currentMembership = () => {
|
|
17
14
|
const currentOrg = getCurrentOrg();
|
|
18
15
|
if (!currentOrg) {
|
|
@@ -23,7 +20,7 @@ function createPermissions(definitions) {
|
|
|
23
20
|
return { ...membership, role: currentOrg.role };
|
|
24
21
|
};
|
|
25
22
|
const hasPermission = (permission) => {
|
|
26
|
-
|
|
23
|
+
assertPermission(definitions, permission);
|
|
27
24
|
if (!context.currentUser) {
|
|
28
25
|
return false;
|
|
29
26
|
}
|
|
@@ -48,16 +45,10 @@ function createPermissions(definitions) {
|
|
|
48
45
|
withPermissions,
|
|
49
46
|
canGrantRole(role) {
|
|
50
47
|
const membership = context.currentUser ? currentMembership() : void 0;
|
|
51
|
-
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
const held = permissionsOf(membership);
|
|
55
|
-
return definitions.roles[role].every(
|
|
56
|
-
(permission) => held.includes(permission)
|
|
57
|
-
);
|
|
48
|
+
return !!membership && coversRole(definitions, permissionsOf(membership), role);
|
|
58
49
|
},
|
|
59
50
|
requirePermission(permission) {
|
|
60
|
-
|
|
51
|
+
assertPermission(definitions, permission);
|
|
61
52
|
if (!context.currentUser) {
|
|
62
53
|
throw new AuthenticationError("You don't have permission to do that.");
|
|
63
54
|
}
|
|
@@ -73,6 +64,5 @@ function createPermissions(definitions) {
|
|
|
73
64
|
};
|
|
74
65
|
}
|
|
75
66
|
export {
|
|
76
|
-
createPermissions
|
|
77
|
-
definePermissions
|
|
67
|
+
createPermissions
|
|
78
68
|
};
|
|
@@ -36,7 +36,7 @@ export interface CreateTenancyOptions {
|
|
|
36
36
|
* Makes the `tenancy` to pass to the `Router`. The router then renders the
|
|
37
37
|
* organization's provider itself, around the layouts and page of every
|
|
38
38
|
* `PrivateSet` whose route is in an organization (by default, one with
|
|
39
|
-
* `{orgSlug}` in its path), and checks `
|
|
39
|
+
* `{orgSlug}` in its path), and checks `permission` on those sets in it.
|
|
40
40
|
*
|
|
41
41
|
* Call it once, at the top level of a module, like `web/src/tenancy.tsx`:
|
|
42
42
|
* a new tenancy on each render would remount the provider.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/tenancy",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3571",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -15,12 +15,6 @@
|
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
-
"./permissions": {
|
|
19
|
-
"default": {
|
|
20
|
-
"types": "./dist/definePermissions.d.ts",
|
|
21
|
-
"default": "./dist/definePermissions.js"
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
18
|
"./web": {
|
|
25
19
|
"default": {
|
|
26
20
|
"types": "./dist/web/index.d.ts",
|
|
@@ -44,17 +38,18 @@
|
|
|
44
38
|
"test:watch": "vitest watch"
|
|
45
39
|
},
|
|
46
40
|
"dependencies": {
|
|
47
|
-
"@cedarjs/api": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
41
|
+
"@cedarjs/api": "8.0.0-canary.3571",
|
|
42
|
+
"@cedarjs/context": "8.0.0-canary.3571",
|
|
43
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3571",
|
|
44
|
+
"@cedarjs/permissions": "8.0.0-canary.3571"
|
|
50
45
|
},
|
|
51
46
|
"devDependencies": {
|
|
52
47
|
"@apollo/client": "4.3.5",
|
|
53
48
|
"@arethetypeswrong/cli": "0.18.5",
|
|
54
|
-
"@cedarjs/auth": "8.0.0-canary.
|
|
55
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
56
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
57
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
49
|
+
"@cedarjs/auth": "8.0.0-canary.3571",
|
|
50
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3571",
|
|
51
|
+
"@cedarjs/router": "8.0.0-canary.3571",
|
|
52
|
+
"@cedarjs/web": "8.0.0-canary.3571",
|
|
58
53
|
"@prisma/adapter-better-sqlite3": "7.10.1",
|
|
59
54
|
"@prisma/client": "7.10.1",
|
|
60
55
|
"@testing-library/dom": "10.4.2",
|
|
@@ -80,9 +75,9 @@
|
|
|
80
75
|
},
|
|
81
76
|
"peerDependencies": {
|
|
82
77
|
"@apollo/client": "4.3.5",
|
|
83
|
-
"@cedarjs/auth": "8.0.0-canary.
|
|
84
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
85
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
78
|
+
"@cedarjs/auth": "8.0.0-canary.3571",
|
|
79
|
+
"@cedarjs/router": "8.0.0-canary.3571",
|
|
80
|
+
"@cedarjs/web": "8.0.0-canary.3571",
|
|
86
81
|
"@prisma/client": "7.10.1",
|
|
87
82
|
"react": "19.3.1"
|
|
88
83
|
},
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Every permission an app checks, and what the built-in roles may do.
|
|
3
|
-
*
|
|
4
|
-
* Plain data with no api code in it, so both sides can import it: the api to
|
|
5
|
-
* check permissions (`createPermissions`), and the web for the names.
|
|
6
|
-
*/
|
|
7
|
-
export interface PermissionDefinitions<Name extends string = string> {
|
|
8
|
-
/**
|
|
9
|
-
* Every permission the app checks, by name. Fields, services and the web
|
|
10
|
-
* ask for these names, so they're defined in code. A name is a stable id,
|
|
11
|
-
* so it can also key what an app shows for it, such as translations.
|
|
12
|
-
*/
|
|
13
|
-
permissions: readonly Name[];
|
|
14
|
-
/**
|
|
15
|
-
* What each built-in role may do in an organization. A role that isn't
|
|
16
|
-
* listed has no permissions.
|
|
17
|
-
*/
|
|
18
|
-
roles: Readonly<Record<string, readonly Name[]>>;
|
|
19
|
-
}
|
|
20
|
-
/** The permission names in `definitions` */
|
|
21
|
-
export type PermissionName<Definitions> = Definitions extends PermissionDefinitions<infer Name> ? Name : never;
|
|
22
|
-
/**
|
|
23
|
-
* Throws when a role lists a permission that `permissions` doesn't define. The types catch this in TypeScript; this is for
|
|
24
|
-
* JavaScript, and for definitions built at runtime.
|
|
25
|
-
*/
|
|
26
|
-
export declare function assertPermissionDefinitions(definitions: PermissionDefinitions): void;
|
|
27
|
-
/**
|
|
28
|
-
* Defines an app's permissions, and what each built-in role may do.
|
|
29
|
-
* Permission names are typed from `permissions`, so a role listing a name
|
|
30
|
-
* that isn't defined is a type error.
|
|
31
|
-
*
|
|
32
|
-
* ```ts
|
|
33
|
-
* export const permissions = definePermissions({
|
|
34
|
-
* permissions: ['projects.read', 'members.manage'],
|
|
35
|
-
* roles: {
|
|
36
|
-
* owner: ['projects.read', 'members.manage'],
|
|
37
|
-
* member: ['projects.read'],
|
|
38
|
-
* },
|
|
39
|
-
* })
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* Pass the result to `createPermissions` on the api.
|
|
43
|
-
*/
|
|
44
|
-
export declare function definePermissions<const Name extends string>(definitions: {
|
|
45
|
-
permissions: readonly Name[];
|
|
46
|
-
roles: Readonly<Record<string, readonly NoInfer<Name>[]>>;
|
|
47
|
-
}): PermissionDefinitions<Name>;
|
|
48
|
-
//# sourceMappingURL=definePermissions.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"definePermissions.d.ts","sourceRoot":"","sources":["../src/definePermissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACjE;;;;OAIG;IACH,WAAW,EAAE,SAAS,IAAI,EAAE,CAAA;IAC5B;;;OAGG;IACH,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAA;CACjD;AAED,4CAA4C;AAC5C,MAAM,MAAM,cAAc,CAAC,WAAW,IACpC,WAAW,SAAS,qBAAqB,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,CAAA;AAEtE;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,qBAAqB,QAanC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,WAAW,EAAE;IACxE,WAAW,EAAE,SAAS,IAAI,EAAE,CAAA;IAC5B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;CAC1D,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAI9B"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
function assertPermissionDefinitions(definitions) {
|
|
2
|
-
const defined = new Set(definitions.permissions);
|
|
3
|
-
for (const [role, names] of Object.entries(definitions.roles)) {
|
|
4
|
-
for (const name of names) {
|
|
5
|
-
if (!defined.has(name)) {
|
|
6
|
-
throw new Error(
|
|
7
|
-
`Unknown permission "${name}" in role "${role}". Add it to \`permissions\` in your permission definitions first.`
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
function definePermissions(definitions) {
|
|
14
|
-
assertPermissionDefinitions(definitions);
|
|
15
|
-
return definitions;
|
|
16
|
-
}
|
|
17
|
-
export {
|
|
18
|
-
assertPermissionDefinitions,
|
|
19
|
-
definePermissions
|
|
20
|
-
};
|