@cedarjs/tenancy 8.0.0-canary.3565 → 8.0.0-canary.3568

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
@@ -73,44 +73,39 @@ import {
73
73
  - `getCurrentOrg()` / `requireCurrentOrg()` — read the organization set on the
74
74
  current request; the latter throws `TenantScopeError` when none is set.
75
75
 
76
- ## Auth helpers
76
+ ## User checks
77
77
 
78
- ```ts
79
- // api/src/lib/auth.ts, in place of the single-tenant hasRole/requireAuth
80
- import { createTenancyAuth } from '@cedarjs/tenancy'
81
-
82
- export const { hasUserRole, hasOrgRole, requireUserAuth, requireOrgAuth } =
83
- createTenancyAuth()
84
-
85
- // In a service:
86
- requireOrgAuth({ roles: ['owner', 'admin'] }) // in the current organization
87
- requireUserAuth() // signed in, no organization needed
88
-
89
- // Anywhere:
90
- if (hasOrgRole('owner')) { ... } // in the current organization
91
- if (hasUserRole('superuser')) { ... } // currentUser.roles
92
- ```
93
-
94
- `requireOrgAuth` (and the `@requireOrgAuth` directive `setup tenancy` adds) is
95
- about the current organization: it throws `AuthenticationError` with no
96
- current user, and `ForbiddenError` with no current organization or when the
97
- user's role there isn't one of `roles` when they're given. With no `roles`,
98
- any member gets in. `requireUserAuth` (and `@requireUserAuth`) is for fields about the user
99
- rather than an organization: it needs a signed-in user, and checks `roles`
100
- against `currentUser.roles`. `hasOrgRole` and `hasUserRole` are the boolean
101
- checks to match. There's no `requireAuth` or `hasRole`.
78
+ `hasRole` and `requireAuth` in `api/src/lib/auth.ts`, and the `@requireAuth`
79
+ directive, are for fields about the user rather than an organization: their
80
+ account, their memberships, accepting an invitation. `requireAuth` needs a
81
+ signed-in user, and both check `roles` against `currentUser.roles`: the user's own roles, in
82
+ any organization or none. There's no role check on the current
83
+ organization: fields about an organization ask for a permission (below).
102
84
 
103
85
  ## Permissions
104
86
 
87
+ Roles in an organization are named bundles of permissions, defined in one
88
+ place. Fields, services and components check a permission in an
89
+ organization, never the membership's role, so a role can do exactly what its
90
+ list says. `hasRole` and `<PrivateSet roles>` check the user's own roles, in
91
+ `currentUser.roles`.
92
+
105
93
  ```ts
106
94
  // packages/permissions/src/index.ts, shared by the api and web sides
107
95
  import { definePermissions } from '@cedarjs/tenancy/permissions'
108
96
 
97
+ const member = ['organization.read', 'projects.read'] as const
98
+
109
99
  export const permissions = definePermissions({
110
- permissions: ['projects.read', 'projects.edit', 'members.manage'],
100
+ permissions: [
101
+ 'organization.read',
102
+ 'projects.read',
103
+ 'projects.edit',
104
+ 'members.manage',
105
+ ],
111
106
  roles: {
112
- owner: ['projects.read', 'projects.edit', 'members.manage'],
113
- member: ['projects.read'],
107
+ owner: [...member, 'projects.edit', 'members.manage'],
108
+ member,
114
109
  },
115
110
  })
116
111
  ```
@@ -121,28 +116,40 @@ import { createPermissions } from '@cedarjs/tenancy'
121
116
 
122
117
  import { permissions } from '@my-app/permissions'
123
118
 
124
- export const { hasPermission, requirePermission, withPermissions } =
125
- createPermissions(permissions)
119
+ export const {
120
+ hasPermission,
121
+ requirePermission,
122
+ canGrantRole,
123
+ withPermissions,
124
+ } = createPermissions(permissions)
126
125
  ```
127
126
 
128
- Roles map to permissions in one place; fields ask for a permission with
129
- `@requirePermission(permission: "projects.edit")` or `requirePermission()`.
130
- A role only gets what its list says, so a new role is one entry. Permission
131
- names are typed from the definitions. `withPermissions` in `getCurrentUser` gives each
132
- membership its permissions, which `useAuth().hasPermission()` checks inside
133
- an organization route. Every permission is about an organization: fields about the user
134
- rather than an organization use `@requireUserAuth`.
127
+ Fields ask for a permission with `@requirePermission(permission:
128
+ "projects.edit")`, and services with `requirePermission('projects.edit')`.
129
+ "Any member" is a permission every role lists, such as `organization.read`,
130
+ so a new role starts with nothing until its entry says otherwise. Permission
131
+ names are typed from the definitions, and an unknown name throws.
132
+
133
+ `canGrantRole(role)` is whether the signed-in user may give someone `role`,
134
+ or take it away: they hold every permission it has. Nobody can hand out more
135
+ than they may do themselves, and a new role needs no grant table. It
136
+ doesn't check `members.manage`: whoever calls it checks that first, as the
137
+ generated `inviteMember` and `removeMember` do with
138
+ `@requirePermission(permission: "members.manage")`.
139
+
140
+ `withPermissions` in `getCurrentUser` gives each membership its permissions,
141
+ which `useAuth().hasPermission()` checks inside an organization route.
135
142
 
136
143
  ## Web
137
144
 
138
145
  `@cedarjs/tenancy/web` exports `createTenancy`, a ready-made `tenancy`,
139
- `useCurrentOrg`, `useOrgBySlug` and `getMemberships`/`hasOrgRole` for the client
146
+ `useCurrentOrg`, `useOrgBySlug` and `getMemberships` for the client
140
147
  side. Pass the tenancy to the router and put organization routes in a
141
148
  `PrivateSet`: the router renders a per-organization Apollo client, keyed by the
142
149
  immutable organization id so switching organizations never mixes caches, around
143
- the set's layouts and page. `useCurrentOrg().hasOrgRole` checks the user's role
144
- in the organization. To guard routes by it, give a `PrivateSet` `orgRoles` or
145
- `orgPermission`, and `unauthorized`:
150
+ the set's layouts and page. `useAuth().hasPermission()` checks what the user
151
+ may do in the organization. To guard routes by a permission of the user's
152
+ membership, give a `PrivateSet` `orgPermission` and `unauthorized`:
146
153
 
147
154
  <!-- prettier-ignore -->
148
155
  ```tsx
@@ -152,9 +159,6 @@ import { tenancy } from 'src/tenancy'
152
159
  <PrivateSet wrap={OrgLayout} unauthenticated="login">
153
160
  <Route path="/org/{orgSlug}/projects" page={ProjectsPage} name="projects" />
154
161
  </PrivateSet>
155
- <PrivateSet wrap={OrgLayout} unauthenticated="login" unauthorized="projects" orgRoles="owner">
156
- <Route path="/org/{orgSlug}/settings" page={SettingsPage} name="settings" />
157
- </PrivateSet>
158
162
  <PrivateSet wrap={OrgLayout} unauthenticated="login" unauthorized="projects" orgPermission="members.manage">
159
163
  <Route path="/org/{orgSlug}/members" page={MembersPage} name="members" />
160
164
  </PrivateSet>
@@ -169,12 +173,13 @@ export const tenancy = createTenancy({
169
173
  })
170
174
  ```
171
175
 
172
- The router checks the role and permission in the organization of the route's
173
- `orgSlug` param. A `PrivateSet` with `orgRoles` or `orgPermission` throws when
176
+ The router checks the permission in the organization that `tenancy.useOrg`
177
+ 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
174
179
  the router has no `tenancy` or its `useOrg` finds no organization for the
175
180
  route; with the default lookup, that's a route without `{orgSlug}`. A route
176
181
  with `{orgSlug}` outside a `PrivateSet` is a public page with no organization
177
- context. `useAuth().hasUserRole` and `<PrivateSet userRoles>` check
178
- `currentUser.roles`; there's no `useAuth().hasRole` or `<PrivateSet roles>` in
179
- an app with tenancy. See the
182
+ context. `useAuth().hasRole` and `<PrivateSet roles>` check
183
+ `currentUser.roles`, the user's own roles, as in an app without tenancy;
184
+ checks in an organization use a permission. See the
180
185
  how-to for the full setup `yarn cedar setup tenancy` generates.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from './errors.js';
2
2
  export * from './context.js';
3
- export * from './auth.js';
4
3
  export * from './permissions.js';
5
4
  export { createTenancyExtension } from './prismaExtension.js';
6
5
  export type { ModelNamesFor, TenancyConfig, UnscopedClient, } from './prismaExtension.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from "./errors.js";
2
2
  export * from "./context.js";
3
- export * from "./auth.js";
4
3
  export * from "./permissions.js";
5
4
  import { createTenancyExtension } from "./prismaExtension.js";
6
5
  export {
@@ -21,9 +21,17 @@ export interface Permissions<Permission extends string> {
21
21
  * `AuthenticationError` when no one is signed in, and `ForbiddenError`
22
22
  * when they may not.
23
23
  */
24
- requirePermission(options: {
25
- permission: Permission;
26
- }): void;
24
+ requirePermission(permission: Permission): void;
25
+ /**
26
+ * Whether the signed-in user may give `role` to someone in the current
27
+ * organization, or take it away: they hold every permission `role` has,
28
+ * so no one can hand out more than they may do themselves. False for a
29
+ * role the definitions don't list.
30
+ *
31
+ * This is only the role half: who may manage members at all is a
32
+ * permission of its own (`members.manage`, say) on the field.
33
+ */
34
+ canGrantRole(role: string): boolean;
27
35
  /**
28
36
  * What a membership may do: its role's permissions. The one place a membership's permissions are worked out, which
29
37
  * `hasPermission`, `requirePermission` and `withPermissions` all use.
@@ -55,7 +63,7 @@ export interface Permissions<Permission extends string> {
55
63
  * // api/src/lib/permissions.ts
56
64
  * import { permissions } from '@my-app/permissions'
57
65
  *
58
- * export const { hasPermission, requirePermission, withPermissions } =
66
+ * export const { hasPermission, requirePermission, canGrantRole, withPermissions } =
59
67
  * createPermissions(permissions)
60
68
  * ```
61
69
  *
@@ -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,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D;;;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,CAwFzB"}
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"}
@@ -46,7 +46,17 @@ function createPermissions(definitions) {
46
46
  hasPermission,
47
47
  permissionsOf,
48
48
  withPermissions,
49
- requirePermission({ permission }) {
49
+ canGrantRole(role) {
50
+ 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
+ );
58
+ },
59
+ requirePermission(permission) {
50
60
  assertDefined(permission);
51
61
  if (!context.currentUser) {
52
62
  throw new AuthenticationError("You don't have permission to do that.");
@@ -36,8 +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 `orgRoles` and `orgPermission` on
40
- * those sets.
39
+ * `{orgSlug}` in its path), and checks `orgPermission` on those sets.
41
40
  *
42
41
  * Call it once, at the top level of a module, like `web/src/tenancy.tsx`:
43
42
  * a new tenancy on each render would remount the provider.
@@ -1 +1 @@
1
- {"version":3,"file":"createTenancy.d.ts","sourceRoot":"","sources":["../../src/web/createTenancy.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAoB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AASxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAOlD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC3B,SAAS,GAAG,IAAI,CAAA;AAErB,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC;AAiJD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAwBzE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,SAAkB,CAAA"}
1
+ {"version":3,"file":"createTenancy.d.ts","sourceRoot":"","sources":["../../src/web/createTenancy.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAoB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAQxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAOlD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC3B,SAAS,GAAG,IAAI,CAAA;AAErB,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC;AA+ID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAwBzE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,SAAkB,CAAA"}
@@ -5,7 +5,6 @@ import { HasPermissionContext } from "@cedarjs/auth";
5
5
  import { navigate, useLocation, useParams } from "@cedarjs/router";
6
6
  import { useCreateApolloClient } from "@cedarjs/web/apollo";
7
7
  import { isOrgMembership } from "./getMemberships.js";
8
- import { hasOrgRole } from "./hasOrgRole.js";
9
8
  import { getOrgClient } from "./orgClients.js";
10
9
  import { OrgContext } from "./OrgContext.js";
11
10
  import { useOrgBySlug } from "./useOrgBySlug.js";
@@ -88,7 +87,6 @@ function OrgProviderImplementation({
88
87
  const contextValue = {
89
88
  org,
90
89
  memberships,
91
- hasOrgRole: (roles, organizationId) => hasOrgRole(memberships, roles, organizationId ?? org.id),
92
90
  setOrg
93
91
  };
94
92
  return /* @__PURE__ */ jsx(ApolloProvider, { client: orgClient, children: /* @__PURE__ */ jsx(OrgContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(HasPermissionContext.Provider, { value: hasPermission, children }) }) });
@@ -1,7 +1,6 @@
1
1
  export type { CreateTenancyOptions, UseTenancyOrg } from './createTenancy.js';
2
2
  export { createTenancy, tenancy } from './createTenancy.js';
3
3
  export { getMemberships } from './getMemberships.js';
4
- export { hasOrgRole } from './hasOrgRole.js';
5
4
  export { clearOrgClients } from './orgClients.js';
6
5
  export type { CurrentOrgSummary, OrgContextValue, OrgMembership, } from './types.js';
7
6
  export { useCurrentOrg } from './useCurrentOrg.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/web/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { createTenancy, tenancy } from "./createTenancy.js";
2
2
  import { getMemberships } from "./getMemberships.js";
3
- import { hasOrgRole } from "./hasOrgRole.js";
4
3
  import { clearOrgClients } from "./orgClients.js";
5
4
  import { useCurrentOrg } from "./useCurrentOrg.js";
6
5
  import { useOrgBySlug } from "./useOrgBySlug.js";
@@ -8,7 +7,6 @@ export {
8
7
  clearOrgClients,
9
8
  createTenancy,
10
9
  getMemberships,
11
- hasOrgRole,
12
10
  tenancy,
13
11
  useCurrentOrg,
14
12
  useOrgBySlug
@@ -38,7 +38,6 @@ export interface CurrentOrgSummary {
38
38
  export interface OrgContextValue {
39
39
  org: CurrentOrgSummary | undefined;
40
40
  memberships: OrgMembership[];
41
- hasOrgRole(roles: string | string[], organizationId?: string): boolean;
42
41
  setOrg(idOrSlug: string): void;
43
42
  }
44
43
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/web/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE;QACZ,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,iBAAiB,GAAG,SAAS,CAAA;IAClC,WAAW,EAAE,aAAa,EAAE,CAAA;IAC5B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACtE,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/web/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE;QACZ,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,iBAAiB,GAAG,SAAS,CAAA;IAClC,WAAW,EAAE,aAAa,EAAE,CAAA;IAC5B,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/tenancy",
3
- "version": "8.0.0-canary.3565",
3
+ "version": "8.0.0-canary.3568",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -44,17 +44,17 @@
44
44
  "test:watch": "vitest watch"
45
45
  },
46
46
  "dependencies": {
47
- "@cedarjs/api": "8.0.0-canary.3565",
48
- "@cedarjs/context": "8.0.0-canary.3565",
49
- "@cedarjs/graphql-server": "8.0.0-canary.3565"
47
+ "@cedarjs/api": "8.0.0-canary.3568",
48
+ "@cedarjs/context": "8.0.0-canary.3568",
49
+ "@cedarjs/graphql-server": "8.0.0-canary.3568"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@apollo/client": "4.3.5",
53
53
  "@arethetypeswrong/cli": "0.18.5",
54
- "@cedarjs/auth": "8.0.0-canary.3565",
55
- "@cedarjs/framework-tools": "8.0.0-canary.3565",
56
- "@cedarjs/router": "8.0.0-canary.3565",
57
- "@cedarjs/web": "8.0.0-canary.3565",
54
+ "@cedarjs/auth": "8.0.0-canary.3568",
55
+ "@cedarjs/framework-tools": "8.0.0-canary.3568",
56
+ "@cedarjs/router": "8.0.0-canary.3568",
57
+ "@cedarjs/web": "8.0.0-canary.3568",
58
58
  "@prisma/adapter-better-sqlite3": "7.10.1",
59
59
  "@prisma/client": "7.10.1",
60
60
  "@testing-library/dom": "10.4.2",
@@ -80,9 +80,9 @@
80
80
  },
81
81
  "peerDependencies": {
82
82
  "@apollo/client": "4.3.5",
83
- "@cedarjs/auth": "8.0.0-canary.3565",
84
- "@cedarjs/router": "8.0.0-canary.3565",
85
- "@cedarjs/web": "8.0.0-canary.3565",
83
+ "@cedarjs/auth": "8.0.0-canary.3568",
84
+ "@cedarjs/router": "8.0.0-canary.3568",
85
+ "@cedarjs/web": "8.0.0-canary.3568",
86
86
  "@prisma/client": "7.10.1",
87
87
  "react": "19.3.1"
88
88
  },
package/dist/auth.d.ts DELETED
@@ -1,65 +0,0 @@
1
- type Roles = string | readonly string[];
2
- /**
3
- * Whether the current request holds one of `roles` in an organization.
4
- *
5
- * With `organizationId`, this checks `context.currentUser.memberships` for a
6
- * membership in that specific organization — useful for checking access to
7
- * an organization other than the one in scope. Without it, this checks the
8
- * role on `context.currentOrg`, the organization already resolved for this
9
- * request.
10
- *
11
- * Returns `false` when there's no authenticated user, no matching
12
- * organization, or no matching membership. An empty `roles` array returns
13
- * `true` as long as a matching membership exists.
14
- */
15
- export declare function hasOrgRole(roles: Roles, organizationId?: string): boolean;
16
- export interface TenancyAuth {
17
- /**
18
- * Whether the signed-in user holds one of `roles` in `currentUser.roles`,
19
- * in any organization or none. What the single-tenant `hasRole()` checked;
20
- * `hasOrgRole()` checks the role in an organization.
21
- */
22
- hasUserRole(roles: Roles): boolean;
23
- /**
24
- * Guards a service or directive on the current organization: signed in,
25
- * a member of the current organization and, when `roles` are given, with
26
- * one of them there. With no `roles`, any member gets in, as any
27
- * signed-in user gets past the single-tenant `requireAuth()`: use `roles`
28
- * or `requirePermission()` for anything narrower.
29
- *
30
- * Throws `AuthenticationError` when no one is signed in, and
31
- * `ForbiddenError` otherwise.
32
- */
33
- requireOrgAuth(options?: {
34
- roles?: Roles;
35
- }): void;
36
- /**
37
- * Guards a service or directive on the signed-in user alone, in any
38
- * organization or none: their account, their memberships, accepting an
39
- * invitation. When `roles` are given, the user needs one of them in
40
- * `currentUser.roles`.
41
- *
42
- * Throws `AuthenticationError` when no one is signed in, and
43
- * `ForbiddenError` otherwise.
44
- */
45
- requireUserAuth(options?: {
46
- roles?: Roles;
47
- }): void;
48
- /** The standalone `hasOrgRole`: with no roles, any membership */
49
- hasOrgRole(roles?: Roles, organizationId?: string): boolean;
50
- }
51
- /**
52
- * The auth checks for an app with tenancy, which `api/src/lib/auth.ts`
53
- * exports in place of its single-tenant `hasRole` and `requireAuth`:
54
- *
55
- * ```ts
56
- * export const { hasUserRole, hasOrgRole, requireUserAuth, requireOrgAuth } =
57
- * createTenancyAuth()
58
- * ```
59
- *
60
- * There's no `hasRole` or `requireAuth`: every check says whether it's
61
- * about the signed-in user alone or the current organization.
62
- */
63
- export declare function createTenancyAuth(): TenancyAuth;
64
- export {};
65
- //# sourceMappingURL=auth.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAKA,KAAK,KAAK,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;AAwBvC;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CA4BzE;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAA;IAClC;;;;;;;;;OASG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,IAAI,CAAA;IACjD;;;;;;;;OAQG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,IAAI,CAAA;IAClD,iEAAiE;IACjE,UAAU,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC5D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CA8C/C"}
package/dist/auth.js DELETED
@@ -1,72 +0,0 @@
1
- import { context } from "@cedarjs/context";
2
- import { AuthenticationError, ForbiddenError } from "@cedarjs/graphql-server";
3
- import { getCurrentOrg, isUserWithMemberships } from "./context.js";
4
- const toList = (roles) => roles === void 0 ? [] : typeof roles === "string" ? [roles] : [...roles];
5
- function userRoles() {
6
- const roles = context.currentUser?.roles;
7
- if (typeof roles === "string") {
8
- return [roles];
9
- }
10
- if (Array.isArray(roles)) {
11
- return roles.filter((role) => typeof role === "string");
12
- }
13
- return [];
14
- }
15
- function hasOrgRole(roles, organizationId) {
16
- const roleList = toList(roles);
17
- if (organizationId) {
18
- const currentUser = context.currentUser;
19
- if (!isUserWithMemberships(currentUser)) {
20
- return false;
21
- }
22
- const membership = currentUser.memberships.find(
23
- (m) => m.organizationId === organizationId
24
- );
25
- if (!membership) {
26
- return false;
27
- }
28
- return roleList.length === 0 || roleList.includes(membership.role);
29
- }
30
- const currentOrg = getCurrentOrg();
31
- if (!currentOrg) {
32
- return false;
33
- }
34
- return roleList.length === 0 || roleList.includes(currentOrg.role);
35
- }
36
- function createTenancyAuth() {
37
- return {
38
- hasUserRole(roles) {
39
- const roleList = toList(roles);
40
- return !!context.currentUser && userRoles().some((role) => roleList.includes(role));
41
- },
42
- requireUserAuth({ roles } = {}) {
43
- if (!context.currentUser) {
44
- throw new AuthenticationError("You don't have permission to do that.");
45
- }
46
- if (roles && !userRoles().some((role) => toList(roles).includes(role))) {
47
- throw new ForbiddenError("You don't have access to do that.");
48
- }
49
- },
50
- requireOrgAuth({ roles } = {}) {
51
- if (!context.currentUser) {
52
- throw new AuthenticationError("You don't have permission to do that.");
53
- }
54
- const currentOrg = getCurrentOrg();
55
- if (!currentOrg) {
56
- throw new ForbiddenError(
57
- "You must be a member of an organization to do that."
58
- );
59
- }
60
- if (roles && !toList(roles).includes(currentOrg.role)) {
61
- throw new ForbiddenError("You don't have access to do that.");
62
- }
63
- },
64
- hasOrgRole(roles = [], organizationId) {
65
- return hasOrgRole(roles, organizationId);
66
- }
67
- };
68
- }
69
- export {
70
- createTenancyAuth,
71
- hasOrgRole
72
- };
@@ -1,12 +0,0 @@
1
- import type { OrgMembership } from './types.js';
2
- /**
3
- * Pure role check over a memberships snapshot, with no dependency on auth or
4
- * context. `useCurrentOrg().hasOrgRole` and the app's own code (checking a
5
- * membership other than the current organization's) both build on this.
6
- *
7
- * Returns `false` when there is no membership in `organizationId`. An empty
8
- * `roles` list returns `true` when a membership exists, matching
9
- * `@cedarjs/tenancy`'s server-side `hasOrgRole`.
10
- */
11
- export declare function hasOrgRole(memberships: readonly OrgMembership[] | undefined, roles: string | string[], organizationId: string): boolean;
12
- //# sourceMappingURL=hasOrgRole.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hasOrgRole.d.ts","sourceRoot":"","sources":["../../src/web/hasOrgRole.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,WAAW,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,EACjD,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,cAAc,EAAE,MAAM,GACrB,OAAO,CAgBT"}
@@ -1,16 +0,0 @@
1
- function hasOrgRole(memberships, roles, organizationId) {
2
- const membership = memberships?.find(
3
- (candidate) => candidate.organizationId === organizationId
4
- );
5
- if (!membership) {
6
- return false;
7
- }
8
- const roleList = Array.isArray(roles) ? roles : [roles];
9
- if (roleList.length === 0) {
10
- return true;
11
- }
12
- return roleList.includes(membership.role);
13
- }
14
- export {
15
- hasOrgRole
16
- };