@cedarjs/tenancy 8.0.0-canary.3538 → 8.0.0-canary.3540
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 +56 -8
- package/dist/auth.d.ts +49 -12
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +46 -21
- package/dist/definePermissions.d.ts +48 -0
- package/dist/definePermissions.d.ts.map +1 -0
- package/dist/definePermissions.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/permissions.d.ts +69 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +68 -0
- package/dist/web/OrgScope.d.ts +26 -5
- package/dist/web/OrgScope.d.ts.map +1 -1
- package/dist/web/OrgScope.js +47 -6
- package/dist/web/getMemberships.d.ts.map +1 -1
- package/dist/web/getMemberships.js +2 -1
- 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 +9 -1
- package/dist/web/types.d.ts.map +1 -1
- package/package.json +17 -11
package/README.md
CHANGED
|
@@ -76,23 +76,71 @@ import {
|
|
|
76
76
|
## Auth helpers
|
|
77
77
|
|
|
78
78
|
```ts
|
|
79
|
-
|
|
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()
|
|
80
84
|
|
|
81
85
|
// In a service:
|
|
82
|
-
|
|
86
|
+
requireOrgAuth({ roles: ['owner', 'admin'] }) // in the current organization
|
|
87
|
+
requireUserAuth() // signed in, no organization needed
|
|
83
88
|
|
|
84
89
|
// Anywhere:
|
|
85
|
-
if (hasOrgRole('owner')) { ... }
|
|
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`.
|
|
102
|
+
|
|
103
|
+
## Permissions
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// packages/permissions/src/index.ts, shared by the api and web sides
|
|
107
|
+
import { definePermissions } from '@cedarjs/tenancy/permissions'
|
|
108
|
+
|
|
109
|
+
export const permissions = definePermissions({
|
|
110
|
+
permissions: ['projects.read', 'projects.edit', 'members.manage'],
|
|
111
|
+
roles: {
|
|
112
|
+
owner: ['projects.read', 'projects.edit', 'members.manage'],
|
|
113
|
+
member: ['projects.read'],
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// api/src/lib/permissions.ts
|
|
120
|
+
import { createPermissions } from '@cedarjs/tenancy'
|
|
121
|
+
|
|
122
|
+
import { permissions } from '@my-app/permissions'
|
|
123
|
+
|
|
124
|
+
export const { hasPermission, requirePermission, withPermissions } =
|
|
125
|
+
createPermissions(permissions)
|
|
86
126
|
```
|
|
87
127
|
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
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
|
+
`OrgScope`. Every permission is about an organization: fields about the user
|
|
134
|
+
rather than an organization use `@requireUserAuth`.
|
|
91
135
|
|
|
92
136
|
## Web
|
|
93
137
|
|
|
94
|
-
`@cedarjs/tenancy/web` exports `OrgScope`, `useCurrentOrg
|
|
138
|
+
`@cedarjs/tenancy/web` exports `OrgScope`, `useCurrentOrg` and
|
|
95
139
|
`getMemberships`/`hasOrgRole` for the client side: a per-organization Apollo
|
|
96
140
|
client provided under organization routes, keyed by the immutable organization
|
|
97
|
-
id so switching organizations never mixes caches.
|
|
141
|
+
id so switching organizations never mixes caches. `useCurrentOrg().hasOrgRole`
|
|
142
|
+
checks the user's role in the organization, and `OrgScope`'s `roles` and
|
|
143
|
+
`unauthorized` props guard routes by it. `useAuth().hasUserRole` checks
|
|
144
|
+
`currentUser.roles`; there's no `useAuth().hasRole` or `<PrivateSet roles>` in
|
|
145
|
+
an app with tenancy. See the how-to for the full
|
|
98
146
|
`<Set wrap={OrgScope}>` setup `yarn cedar setup tenancy` generates.
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type Roles = string | readonly string[];
|
|
1
2
|
/**
|
|
2
3
|
* Whether the current request holds one of `roles` in an organization.
|
|
3
4
|
*
|
|
@@ -9,20 +10,56 @@
|
|
|
9
10
|
*
|
|
10
11
|
* Returns `false` when there's no authenticated user, no matching
|
|
11
12
|
* organization, or no matching membership. An empty `roles` array returns
|
|
12
|
-
* `true` as long as a matching membership exists
|
|
13
|
-
* behavior for global roles.
|
|
13
|
+
* `true` as long as a matching membership exists.
|
|
14
14
|
*/
|
|
15
|
-
export declare function hasOrgRole(roles:
|
|
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
|
+
}
|
|
16
51
|
/**
|
|
17
|
-
*
|
|
18
|
-
* in
|
|
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`:
|
|
19
54
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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.
|
|
24
62
|
*/
|
|
25
|
-
export declare function
|
|
26
|
-
|
|
27
|
-
}): void;
|
|
63
|
+
export declare function createTenancyAuth(): TenancyAuth;
|
|
64
|
+
export {};
|
|
28
65
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAKA
|
|
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
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { context } from "@cedarjs/context";
|
|
2
2
|
import { AuthenticationError, ForbiddenError } from "@cedarjs/graphql-server";
|
|
3
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
|
+
}
|
|
4
15
|
function hasOrgRole(roles, organizationId) {
|
|
5
|
-
const roleList =
|
|
16
|
+
const roleList = toList(roles);
|
|
6
17
|
if (organizationId) {
|
|
7
18
|
const currentUser = context.currentUser;
|
|
8
19
|
if (!isUserWithMemberships(currentUser)) {
|
|
@@ -22,26 +33,40 @@ function hasOrgRole(roles, organizationId) {
|
|
|
22
33
|
}
|
|
23
34
|
return roleList.length === 0 || roleList.includes(currentOrg.role);
|
|
24
35
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
};
|
|
43
68
|
}
|
|
44
69
|
export {
|
|
45
|
-
|
|
46
|
-
|
|
70
|
+
createTenancyAuth,
|
|
71
|
+
hasOrgRole
|
|
47
72
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './errors.js';
|
|
2
2
|
export * from './context.js';
|
|
3
3
|
export * from './auth.js';
|
|
4
|
+
export * from './permissions.js';
|
|
4
5
|
export { createTenancyExtension } from './prismaExtension.js';
|
|
5
6
|
export type { ModelNamesFor, TenancyConfig } from './prismaExtension.js';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
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,WAAW,CAAA;AACzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,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,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { PermissionDefinitions } from './definePermissions.js';
|
|
2
|
+
export { definePermissions } from './definePermissions.js';
|
|
3
|
+
export type { PermissionDefinitions, PermissionName, } from './definePermissions.js';
|
|
4
|
+
/** What `permissionsOf` needs to know about a membership */
|
|
5
|
+
export interface MembershipForPermissions {
|
|
6
|
+
role: string;
|
|
7
|
+
}
|
|
8
|
+
/** A membership as `getCurrentUser` returns it, with its permissions */
|
|
9
|
+
export type WithPermissions<Membership, Permission extends string> = Membership & {
|
|
10
|
+
permissions: Permission[];
|
|
11
|
+
};
|
|
12
|
+
export interface Permissions<Permission extends string> {
|
|
13
|
+
/**
|
|
14
|
+
* Whether the signed-in user may do `permission` in this request: their
|
|
15
|
+
* membership in the current organization has it. False with no current
|
|
16
|
+
* organization.
|
|
17
|
+
*/
|
|
18
|
+
hasPermission(permission: Permission): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Guards a service or directive on `hasPermission(permission)`. Throws
|
|
21
|
+
* `AuthenticationError` when no one is signed in, and `ForbiddenError`
|
|
22
|
+
* when they may not.
|
|
23
|
+
*/
|
|
24
|
+
requirePermission(options: {
|
|
25
|
+
permission: Permission;
|
|
26
|
+
}): void;
|
|
27
|
+
/**
|
|
28
|
+
* What a membership may do: its role's permissions. The one place a membership's permissions are worked out, which
|
|
29
|
+
* `hasPermission`, `requirePermission` and `withPermissions` all use.
|
|
30
|
+
*/
|
|
31
|
+
permissionsOf(membership: MembershipForPermissions): Permission[];
|
|
32
|
+
/**
|
|
33
|
+
* `user` with each of its memberships' permissions added, for
|
|
34
|
+
* `getCurrentUser` to return, so the web gets them with the memberships
|
|
35
|
+
* (`useAuth().hasPermission`).
|
|
36
|
+
*/
|
|
37
|
+
withPermissions<User extends {
|
|
38
|
+
memberships: MembershipForPermissions[];
|
|
39
|
+
}>(user: User): Omit<User, 'memberships'> & {
|
|
40
|
+
memberships: WithPermissions<User['memberships'][number], Permission>[];
|
|
41
|
+
};
|
|
42
|
+
withPermissions<User extends {
|
|
43
|
+
memberships: MembershipForPermissions[];
|
|
44
|
+
}>(user: User | null): (Omit<User, 'memberships'> & {
|
|
45
|
+
memberships: WithPermissions<User['memberships'][number], Permission>[];
|
|
46
|
+
}) | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Permissions on top of roles: roles map to permissions in one place, and
|
|
50
|
+
* fields and services ask for a permission instead of listing roles, so
|
|
51
|
+
* adding a role is one entry in the definitions instead of an edit to every
|
|
52
|
+
* field it should reach.
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* // api/src/lib/permissions.ts
|
|
56
|
+
* import { permissions } from '@my-app/permissions'
|
|
57
|
+
*
|
|
58
|
+
* export const { hasPermission, requirePermission, withPermissions } =
|
|
59
|
+
* createPermissions(permissions)
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* `permissions` comes from `definePermissions`. Fails closed: a role without
|
|
63
|
+
* the permission is refused, as is a role the definitions don't list.
|
|
64
|
+
* Permission names are typed, so a typo in a service is a type error; a
|
|
65
|
+
* name that isn't defined (a typo in an SDL directive) throws instead of
|
|
66
|
+
* quietly refusing everyone.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createPermissions<const Permission extends string>(definitions: PermissionDefinitions<Permission>): Permissions<Permission>;
|
|
69
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { context } from "@cedarjs/context";
|
|
2
|
+
import { AuthenticationError, ForbiddenError } from "@cedarjs/graphql-server";
|
|
3
|
+
import { getCurrentOrg, isUserWithMemberships } from "./context.js";
|
|
4
|
+
import { assertPermissionDefinitions } from "./definePermissions.js";
|
|
5
|
+
import { definePermissions } from "./definePermissions.js";
|
|
6
|
+
function createPermissions(definitions) {
|
|
7
|
+
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]] : [];
|
|
16
|
+
const currentMembership = () => {
|
|
17
|
+
const currentOrg = getCurrentOrg();
|
|
18
|
+
if (!currentOrg) {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
const currentUser = context.currentUser;
|
|
22
|
+
const membership = isUserWithMemberships(currentUser) ? currentUser.memberships.find((m) => m.id === currentOrg.membershipId) : void 0;
|
|
23
|
+
return { ...membership, role: currentOrg.role };
|
|
24
|
+
};
|
|
25
|
+
const hasPermission = (permission) => {
|
|
26
|
+
assertDefined(permission);
|
|
27
|
+
if (!context.currentUser) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const membership = currentMembership();
|
|
31
|
+
return !!membership && permissionsOf(membership).includes(permission);
|
|
32
|
+
};
|
|
33
|
+
function withPermissions(user) {
|
|
34
|
+
if (!user) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...user,
|
|
39
|
+
memberships: user.memberships.map((membership) => ({
|
|
40
|
+
...membership,
|
|
41
|
+
permissions: permissionsOf(membership)
|
|
42
|
+
}))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
hasPermission,
|
|
47
|
+
permissionsOf,
|
|
48
|
+
withPermissions,
|
|
49
|
+
requirePermission({ permission }) {
|
|
50
|
+
assertDefined(permission);
|
|
51
|
+
if (!context.currentUser) {
|
|
52
|
+
throw new AuthenticationError("You don't have permission to do that.");
|
|
53
|
+
}
|
|
54
|
+
if (!getCurrentOrg()) {
|
|
55
|
+
throw new ForbiddenError(
|
|
56
|
+
"You must be a member of an organization to do that."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (!hasPermission(permission)) {
|
|
60
|
+
throw new ForbiddenError("You don't have access to do that.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
createPermissions,
|
|
67
|
+
definePermissions
|
|
68
|
+
};
|
package/dist/web/OrgScope.d.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { UseAuth } from '@cedarjs/auth';
|
|
2
|
+
import type { PermissionName, UseAuth } from '@cedarjs/auth';
|
|
3
3
|
export interface OrgScopeProps {
|
|
4
4
|
/** Overrides the `orgSlug` route param. */
|
|
5
5
|
orgSlug?: string;
|
|
6
6
|
/** Rendered when the user has no membership matching the slug. */
|
|
7
7
|
notAMember?: React.ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Only members with one of these roles in the organization get in.
|
|
10
|
+
* Checked after the organization is resolved, which `<PrivateSet roles>`
|
|
11
|
+
* around `OrgScope` can't do.
|
|
12
|
+
*/
|
|
13
|
+
roles?: string | string[];
|
|
14
|
+
/**
|
|
15
|
+
* Only members whose membership has this permission get in (see
|
|
16
|
+
* `createPermissions` in `@cedarjs/tenancy`). Checked like `roles`.
|
|
17
|
+
*/
|
|
18
|
+
permission?: PermissionName;
|
|
19
|
+
/**
|
|
20
|
+
* The name of the route a member without one of `roles`, or without
|
|
21
|
+
* `permission`, is redirected to. It gets the `orgSlug` param when its
|
|
22
|
+
* path has one, so it can be a page of the same organization. Without
|
|
23
|
+
* it, they get `notAMember`.
|
|
24
|
+
*/
|
|
25
|
+
unauthorized?: string;
|
|
8
26
|
/**
|
|
9
27
|
* The app's `useAuth`, so `OrgScope` can read `currentUser.memberships`
|
|
10
28
|
* without importing app code. Defaults to `useNoAuth`.
|
|
@@ -21,9 +39,12 @@ export interface OrgScopeProps {
|
|
|
21
39
|
* Provides the per-organization Apollo client and `OrgContext` for the
|
|
22
40
|
* organization matching `orgSlug` (a prop, or else the `orgSlug` route
|
|
23
41
|
* param). Renders `notAMember` when the current user has no membership in
|
|
24
|
-
* that organization
|
|
25
|
-
*
|
|
26
|
-
*
|
|
42
|
+
* that organization (or not one with `roles`, see `unauthorized`);
|
|
43
|
+
* otherwise wraps `children` in that organization's own `ApolloProvider`,
|
|
44
|
+
* so every Cell, `useQuery` and `useMutation` under it carries the
|
|
45
|
+
* `cedar-org` header and reads from that organization's cache. Under it,
|
|
46
|
+
* `useAuth().hasPermission` checks the user's membership in that
|
|
47
|
+
* organization.
|
|
27
48
|
*/
|
|
28
|
-
export declare function OrgScope({ orgSlug: orgSlugProp, notAMember, useAuth, onSetOrg, children, }: OrgScopeProps): React.JSX.Element;
|
|
49
|
+
export declare function OrgScope({ orgSlug: orgSlugProp, notAMember, roles, permission, unauthorized, useAuth, onSetOrg, children, }: OrgScopeProps): React.JSX.Element;
|
|
29
50
|
//# sourceMappingURL=OrgScope.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrgScope.d.ts","sourceRoot":"","sources":["../../src/web/OrgScope.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"OrgScope.d.ts","sourceRoot":"","sources":["../../src/web/OrgScope.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAqB5D,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAwCD;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,WAAW,EACpB,UAAiB,EACjB,KAAK,EACL,UAAU,EACV,YAAY,EACZ,OAAmB,EACnB,QAAQ,EACR,QAAQ,GACT,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA8KnC"}
|
package/dist/web/OrgScope.js
CHANGED
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { ApolloProvider } from "@apollo/client/react";
|
|
4
|
-
import { useNoAuth } from "@cedarjs/auth";
|
|
5
|
-
import {
|
|
4
|
+
import { HasPermissionContext, useNoAuth } from "@cedarjs/auth";
|
|
5
|
+
import {
|
|
6
|
+
navigate,
|
|
7
|
+
Redirect,
|
|
8
|
+
routes,
|
|
9
|
+
useLocation,
|
|
10
|
+
useParams
|
|
11
|
+
} from "@cedarjs/router";
|
|
6
12
|
import { useCreateApolloClient } from "@cedarjs/web/apollo";
|
|
7
13
|
import { getMemberships } from "./getMemberships.js";
|
|
8
14
|
import { hasOrgRole } from "./hasOrgRole.js";
|
|
9
15
|
import { clearOrgClients, dropOrgClient, getOrgClient } from "./orgClients.js";
|
|
10
16
|
import { OrgContext } from "./OrgContext.js";
|
|
11
17
|
const CEDAR_ORG_HEADER = "cedar-org";
|
|
18
|
+
const toList = (roles) => Array.isArray(roles) ? roles : [roles];
|
|
19
|
+
const REPLACE = { replace: true };
|
|
20
|
+
function isNamedRoute(value) {
|
|
21
|
+
return typeof value === "function";
|
|
22
|
+
}
|
|
23
|
+
function unauthorizedPath(route, orgSlug) {
|
|
24
|
+
try {
|
|
25
|
+
return route();
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (e instanceof Error && e.message.includes("Missing parameter")) {
|
|
28
|
+
return route({ orgSlug });
|
|
29
|
+
}
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
12
33
|
function membershipRoleMap(memberships) {
|
|
13
34
|
return new Map(memberships.map((m) => [m.organizationId, m.role]));
|
|
14
35
|
}
|
|
15
36
|
function OrgScope({
|
|
16
37
|
orgSlug: orgSlugProp,
|
|
17
38
|
notAMember = null,
|
|
39
|
+
roles,
|
|
40
|
+
permission,
|
|
41
|
+
unauthorized,
|
|
18
42
|
useAuth = useNoAuth,
|
|
19
43
|
onSetOrg,
|
|
20
44
|
children
|
|
@@ -77,15 +101,32 @@ function OrgScope({
|
|
|
77
101
|
[onSetOrg, params, location.pathname, location.search, location.hash]
|
|
78
102
|
);
|
|
79
103
|
const membership = orgSlug ? memberships.find((m) => m.organization.slug === orgSlug) : void 0;
|
|
80
|
-
|
|
104
|
+
const membershipPermissions = membership?.permissions;
|
|
105
|
+
const hasPermission = React.useCallback(
|
|
106
|
+
(permissionToCheck) => membershipPermissions?.includes(permissionToCheck) ?? false,
|
|
107
|
+
[membershipPermissions]
|
|
108
|
+
);
|
|
109
|
+
if (!membership || !userId || !orgSlug) {
|
|
81
110
|
return /* @__PURE__ */ jsx(Fragment, { children: notAMember });
|
|
82
111
|
}
|
|
112
|
+
const allowed = (roles === void 0 || toList(roles).includes(membership.role)) && (permission === void 0 || hasPermission(permission));
|
|
113
|
+
if (!allowed) {
|
|
114
|
+
if (!unauthorized) {
|
|
115
|
+
return /* @__PURE__ */ jsx(Fragment, { children: notAMember });
|
|
116
|
+
}
|
|
117
|
+
const route = Object.hasOwn(routes, unauthorized) ? Reflect.get(routes, unauthorized) : void 0;
|
|
118
|
+
if (!isNamedRoute(route)) {
|
|
119
|
+
throw new Error(`We could not find a route named ${unauthorized}`);
|
|
120
|
+
}
|
|
121
|
+
return /* @__PURE__ */ jsx(Redirect, { to: unauthorizedPath(route, orgSlug), options: REPLACE });
|
|
122
|
+
}
|
|
83
123
|
const org = {
|
|
84
124
|
id: membership.organizationId,
|
|
85
125
|
slug: membership.organization.slug,
|
|
86
126
|
name: membership.organization.name,
|
|
87
127
|
role: membership.role,
|
|
88
|
-
membershipId: membership.id
|
|
128
|
+
membershipId: membership.id,
|
|
129
|
+
permissions: membership.permissions ?? []
|
|
89
130
|
};
|
|
90
131
|
const orgClient = getOrgClient({
|
|
91
132
|
userId,
|
|
@@ -97,10 +138,10 @@ function OrgScope({
|
|
|
97
138
|
const contextValue = {
|
|
98
139
|
org,
|
|
99
140
|
memberships,
|
|
100
|
-
hasOrgRole: (
|
|
141
|
+
hasOrgRole: (roles2, organizationId) => hasOrgRole(memberships, roles2, organizationId ?? org.id),
|
|
101
142
|
setOrg
|
|
102
143
|
};
|
|
103
|
-
return /* @__PURE__ */ jsx(ApolloProvider, { client: orgClient, children: /* @__PURE__ */ jsx(OrgContext.Provider, { value: contextValue, children }) });
|
|
144
|
+
return /* @__PURE__ */ jsx(ApolloProvider, { client: orgClient, children: /* @__PURE__ */ jsx(OrgContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(HasPermissionContext.Provider, { value: hasPermission, children }) }) });
|
|
104
145
|
}
|
|
105
146
|
export {
|
|
106
147
|
OrgScope
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getMemberships.d.ts","sourceRoot":"","sources":["../../src/web/getMemberships.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"getMemberships.d.ts","sourceRoot":"","sources":["../../src/web/getMemberships.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAmC/C;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,OAAO,GAAG,aAAa,EAAE,CAYpE"}
|
|
@@ -8,7 +8,8 @@ function isOrgMembership(value) {
|
|
|
8
8
|
return false;
|
|
9
9
|
}
|
|
10
10
|
const org = organization;
|
|
11
|
-
|
|
11
|
+
const permissions = membership["permissions"];
|
|
12
|
+
return (permissions === void 0 || Array.isArray(permissions) && permissions.every((permission) => typeof permission === "string")) && typeof membership["id"] === "string" && typeof membership["organizationId"] === "string" && typeof membership["role"] === "string" && typeof org["id"] === "string" && typeof org["slug"] === "string" && typeof org["name"] === "string";
|
|
12
13
|
}
|
|
13
14
|
function getMemberships(currentUser) {
|
|
14
15
|
if (!currentUser || typeof currentUser !== "object") {
|
package/dist/web/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { getMemberships } from './getMemberships.js';
|
|
2
2
|
export { hasOrgRole } from './hasOrgRole.js';
|
|
3
|
-
export { OrgContext } from './OrgContext.js';
|
|
4
3
|
export type { OrgScopeProps } from './OrgScope.js';
|
|
5
4
|
export { OrgScope } from './OrgScope.js';
|
|
6
5
|
export { clearOrgClients } from './orgClients.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,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,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"}
|
package/dist/web/index.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { getMemberships } from "./getMemberships.js";
|
|
2
2
|
import { hasOrgRole } from "./hasOrgRole.js";
|
|
3
|
-
import { OrgContext } from "./OrgContext.js";
|
|
4
3
|
import { OrgScope } from "./OrgScope.js";
|
|
5
4
|
import { clearOrgClients } from "./orgClients.js";
|
|
6
5
|
import { useCurrentOrg } from "./useCurrentOrg.js";
|
|
7
6
|
export {
|
|
8
|
-
OrgContext,
|
|
9
7
|
OrgScope,
|
|
10
8
|
clearOrgClients,
|
|
11
9
|
getMemberships,
|
package/dist/web/types.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface OrgMembership {
|
|
|
7
7
|
id: string;
|
|
8
8
|
organizationId: string;
|
|
9
9
|
role: string;
|
|
10
|
+
/**
|
|
11
|
+
* What the membership may do: `withPermissions` from the app's
|
|
12
|
+
* permissions map adds these in `getCurrentUser`. Missing when the app
|
|
13
|
+
* doesn't use permissions.
|
|
14
|
+
*/
|
|
15
|
+
permissions?: string[];
|
|
10
16
|
organization: {
|
|
11
17
|
id: string;
|
|
12
18
|
slug: string;
|
|
@@ -23,9 +29,11 @@ export interface CurrentOrgSummary {
|
|
|
23
29
|
name: string;
|
|
24
30
|
role: string;
|
|
25
31
|
membershipId: string;
|
|
32
|
+
/** The membership's permissions; empty when the app doesn't use them */
|
|
33
|
+
permissions: string[];
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
|
-
*
|
|
36
|
+
* What `OrgScope` provides below it, read through `useCurrentOrg()`.
|
|
29
37
|
*/
|
|
30
38
|
export interface OrgContextValue {
|
|
31
39
|
org: CurrentOrgSummary | undefined;
|
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,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;
|
|
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"}
|
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.3540",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -15,6 +15,12 @@
|
|
|
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
|
+
},
|
|
18
24
|
"./web": {
|
|
19
25
|
"default": {
|
|
20
26
|
"types": "./dist/web/index.d.ts",
|
|
@@ -38,17 +44,17 @@
|
|
|
38
44
|
"test:watch": "vitest watch"
|
|
39
45
|
},
|
|
40
46
|
"dependencies": {
|
|
41
|
-
"@cedarjs/api": "8.0.0-canary.
|
|
42
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
43
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
47
|
+
"@cedarjs/api": "8.0.0-canary.3540",
|
|
48
|
+
"@cedarjs/context": "8.0.0-canary.3540",
|
|
49
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3540"
|
|
44
50
|
},
|
|
45
51
|
"devDependencies": {
|
|
46
52
|
"@apollo/client": "4.3.5",
|
|
47
53
|
"@arethetypeswrong/cli": "0.18.5",
|
|
48
|
-
"@cedarjs/auth": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
50
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
51
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
54
|
+
"@cedarjs/auth": "8.0.0-canary.3540",
|
|
55
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3540",
|
|
56
|
+
"@cedarjs/router": "8.0.0-canary.3540",
|
|
57
|
+
"@cedarjs/web": "8.0.0-canary.3540",
|
|
52
58
|
"@prisma/adapter-better-sqlite3": "7.10.1",
|
|
53
59
|
"@prisma/client": "7.10.1",
|
|
54
60
|
"@testing-library/dom": "10.4.2",
|
|
@@ -74,9 +80,9 @@
|
|
|
74
80
|
},
|
|
75
81
|
"peerDependencies": {
|
|
76
82
|
"@apollo/client": "4.3.5",
|
|
77
|
-
"@cedarjs/auth": "8.0.0-canary.
|
|
78
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
79
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
83
|
+
"@cedarjs/auth": "8.0.0-canary.3540",
|
|
84
|
+
"@cedarjs/router": "8.0.0-canary.3540",
|
|
85
|
+
"@cedarjs/web": "8.0.0-canary.3540",
|
|
80
86
|
"@prisma/client": "7.10.1",
|
|
81
87
|
"react": "19.3.1"
|
|
82
88
|
},
|