@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 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. Fields, services and components check a permission in an
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/tenancy/permissions'
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` `orgPermission` and `unauthorized`:
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" orgPermission="members.manage">
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 `orgPermission` throws when
179
- the router has no `tenancy` or its `useOrg` finds no organization for the
180
- route; with the default lookup, that's a route without `{orgSlug}`. A route
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;
@@ -1,6 +1,4 @@
1
- import type { PermissionDefinitions } from './definePermissions.js';
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":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,YAAY,EACV,qBAAqB,EACrB,cAAc,GACf,MAAM,wBAAwB,CAAA;AAE/B,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,CAqGzB"}
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"}
@@ -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 assertDefined = (permission) => {
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
- assertDefined(permission);
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
- if (!membership || !Object.hasOwn(definitions.roles, role)) {
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
- assertDefined(permission);
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 `orgPermission` on those sets.
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.3569",
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.3569",
48
- "@cedarjs/context": "8.0.0-canary.3569",
49
- "@cedarjs/graphql-server": "8.0.0-canary.3569"
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.3569",
55
- "@cedarjs/framework-tools": "8.0.0-canary.3569",
56
- "@cedarjs/router": "8.0.0-canary.3569",
57
- "@cedarjs/web": "8.0.0-canary.3569",
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.3569",
84
- "@cedarjs/router": "8.0.0-canary.3569",
85
- "@cedarjs/web": "8.0.0-canary.3569",
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
- };