@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 +54 -49
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/permissions.d.ts +12 -4
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +11 -1
- package/dist/web/createTenancy.d.ts +1 -2
- package/dist/web/createTenancy.d.ts.map +1 -1
- package/dist/web/createTenancy.js +0 -2
- package/dist/web/index.d.ts +0 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +0 -2
- package/dist/web/types.d.ts +0 -1
- package/dist/web/types.d.ts.map +1 -1
- package/package.json +11 -11
- package/dist/auth.d.ts +0 -65
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -72
- package/dist/web/hasOrgRole.d.ts +0 -12
- package/dist/web/hasOrgRole.d.ts.map +0 -1
- package/dist/web/hasOrgRole.js +0 -16
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
|
-
##
|
|
76
|
+
## User checks
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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: [
|
|
100
|
+
permissions: [
|
|
101
|
+
'organization.read',
|
|
102
|
+
'projects.read',
|
|
103
|
+
'projects.edit',
|
|
104
|
+
'members.manage',
|
|
105
|
+
],
|
|
111
106
|
roles: {
|
|
112
|
-
owner: [
|
|
113
|
-
member
|
|
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 {
|
|
125
|
-
|
|
119
|
+
export const {
|
|
120
|
+
hasPermission,
|
|
121
|
+
requirePermission,
|
|
122
|
+
canGrantRole,
|
|
123
|
+
withPermissions,
|
|
124
|
+
} = createPermissions(permissions)
|
|
126
125
|
```
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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. `
|
|
144
|
-
in the organization. To guard routes by
|
|
145
|
-
`orgPermission
|
|
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
|
|
173
|
-
|
|
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().
|
|
178
|
-
`currentUser.roles
|
|
179
|
-
an
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
package/dist/permissions.d.ts
CHANGED
|
@@ -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(
|
|
25
|
-
|
|
26
|
-
|
|
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,
|
|
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"}
|
package/dist/permissions.js
CHANGED
|
@@ -46,7 +46,17 @@ function createPermissions(definitions) {
|
|
|
46
46
|
hasPermission,
|
|
47
47
|
permissionsOf,
|
|
48
48
|
withPermissions,
|
|
49
|
-
|
|
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 `
|
|
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;
|
|
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 }) }) });
|
package/dist/web/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/web/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
package/dist/web/types.d.ts
CHANGED
|
@@ -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
|
package/dist/web/types.d.ts.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
48
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
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.
|
|
55
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
56
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
57
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
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.
|
|
84
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
85
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
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
|
package/dist/auth.d.ts.map
DELETED
|
@@ -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
|
-
};
|
package/dist/web/hasOrgRole.d.ts
DELETED
|
@@ -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"}
|
package/dist/web/hasOrgRole.js
DELETED
|
@@ -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
|
-
};
|