@cedarjs/tenancy 8.0.0-canary.3539 → 8.0.0-canary.3541
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 +33 -0
- 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 +14 -6
- package/dist/web/OrgScope.d.ts.map +1 -1
- package/dist/web/OrgScope.js +12 -4
- package/dist/web/getMemberships.d.ts.map +1 -1
- package/dist/web/getMemberships.js +2 -1
- package/dist/web/types.d.ts +8 -0
- package/dist/web/types.d.ts.map +1 -1
- package/package.json +17 -11
package/README.md
CHANGED
|
@@ -100,6 +100,39 @@ rather than an organization: it needs a signed-in user, and checks `roles`
|
|
|
100
100
|
against `currentUser.roles`. `hasOrgRole` and `hasUserRole` are the boolean
|
|
101
101
|
checks to match. There's no `requireAuth` or `hasRole`.
|
|
102
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)
|
|
126
|
+
```
|
|
127
|
+
|
|
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`.
|
|
135
|
+
|
|
103
136
|
## Web
|
|
104
137
|
|
|
105
138
|
`@cedarjs/tenancy/web` exports `OrgScope`, `useCurrentOrg` and
|
|
@@ -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,5 +1,5 @@
|
|
|
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;
|
|
@@ -12,9 +12,15 @@ export interface OrgScopeProps {
|
|
|
12
12
|
*/
|
|
13
13
|
roles?: string | string[];
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
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`.
|
|
18
24
|
*/
|
|
19
25
|
unauthorized?: string;
|
|
20
26
|
/**
|
|
@@ -36,7 +42,9 @@ export interface OrgScopeProps {
|
|
|
36
42
|
* that organization (or not one with `roles`, see `unauthorized`);
|
|
37
43
|
* otherwise wraps `children` in that organization's own `ApolloProvider`,
|
|
38
44
|
* so every Cell, `useQuery` and `useMutation` under it carries the
|
|
39
|
-
* `cedar-org` header and reads from that organization's cache.
|
|
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.
|
|
40
48
|
*/
|
|
41
|
-
export declare function OrgScope({ orgSlug: orgSlugProp, notAMember, roles, unauthorized, 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;
|
|
42
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,7 +1,7 @@
|
|
|
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";
|
|
4
|
+
import { HasPermissionContext, useNoAuth } from "@cedarjs/auth";
|
|
5
5
|
import {
|
|
6
6
|
navigate,
|
|
7
7
|
Redirect,
|
|
@@ -37,6 +37,7 @@ function OrgScope({
|
|
|
37
37
|
orgSlug: orgSlugProp,
|
|
38
38
|
notAMember = null,
|
|
39
39
|
roles,
|
|
40
|
+
permission,
|
|
40
41
|
unauthorized,
|
|
41
42
|
useAuth = useNoAuth,
|
|
42
43
|
onSetOrg,
|
|
@@ -100,10 +101,16 @@ function OrgScope({
|
|
|
100
101
|
[onSetOrg, params, location.pathname, location.search, location.hash]
|
|
101
102
|
);
|
|
102
103
|
const membership = orgSlug ? memberships.find((m) => m.organization.slug === orgSlug) : void 0;
|
|
104
|
+
const membershipPermissions = membership?.permissions;
|
|
105
|
+
const hasPermission = React.useCallback(
|
|
106
|
+
(permissionToCheck) => membershipPermissions?.includes(permissionToCheck) ?? false,
|
|
107
|
+
[membershipPermissions]
|
|
108
|
+
);
|
|
103
109
|
if (!membership || !userId || !orgSlug) {
|
|
104
110
|
return /* @__PURE__ */ jsx(Fragment, { children: notAMember });
|
|
105
111
|
}
|
|
106
|
-
|
|
112
|
+
const allowed = (roles === void 0 || toList(roles).includes(membership.role)) && (permission === void 0 || hasPermission(permission));
|
|
113
|
+
if (!allowed) {
|
|
107
114
|
if (!unauthorized) {
|
|
108
115
|
return /* @__PURE__ */ jsx(Fragment, { children: notAMember });
|
|
109
116
|
}
|
|
@@ -118,7 +125,8 @@ function OrgScope({
|
|
|
118
125
|
slug: membership.organization.slug,
|
|
119
126
|
name: membership.organization.name,
|
|
120
127
|
role: membership.role,
|
|
121
|
-
membershipId: membership.id
|
|
128
|
+
membershipId: membership.id,
|
|
129
|
+
permissions: membership.permissions ?? []
|
|
122
130
|
};
|
|
123
131
|
const orgClient = getOrgClient({
|
|
124
132
|
userId,
|
|
@@ -133,7 +141,7 @@ function OrgScope({
|
|
|
133
141
|
hasOrgRole: (roles2, organizationId) => hasOrgRole(memberships, roles2, organizationId ?? org.id),
|
|
134
142
|
setOrg
|
|
135
143
|
};
|
|
136
|
-
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 }) }) });
|
|
137
145
|
}
|
|
138
146
|
export {
|
|
139
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/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,6 +29,8 @@ 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()`.
|
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.3541",
|
|
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.3541",
|
|
48
|
+
"@cedarjs/context": "8.0.0-canary.3541",
|
|
49
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3541"
|
|
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.3541",
|
|
55
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3541",
|
|
56
|
+
"@cedarjs/router": "8.0.0-canary.3541",
|
|
57
|
+
"@cedarjs/web": "8.0.0-canary.3541",
|
|
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.3541",
|
|
84
|
+
"@cedarjs/router": "8.0.0-canary.3541",
|
|
85
|
+
"@cedarjs/web": "8.0.0-canary.3541",
|
|
80
86
|
"@prisma/client": "7.10.1",
|
|
81
87
|
"react": "19.3.1"
|
|
82
88
|
},
|