@cedarjs/cli 8.0.0-canary.3565 → 8.0.0-canary.3567

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.
@@ -18,8 +18,7 @@ function buildMembershipsSelectProperty() {
18
18
  }
19
19
  return property;
20
20
  }
21
- const TENANCY_AUTH_EXPORT = `export const { hasUserRole, hasOrgRole, requireUserAuth, requireOrgAuth } =
22
- createTenancyAuth()
21
+ const TENANCY_AUTH_EXPORT = `export const { hasUserRole, requireUserAuth } = createTenancyAuth()
23
22
  `;
24
23
  const DEFAULT_REQUIRE_AUTH_STATEMENTS = [
25
24
  /^if \(!isAuthenticated\(\)\) \{ throw new AuthenticationError\([^)]*\);? \}$/,
@@ -59,9 +59,8 @@ export const schema = gql`
59
59
 
60
60
  """
61
61
  Creates a pending membership (an invitation) in the current organization,
62
- for a role the caller's own role may grant (see ROLE_GRANTS in
63
- organizations.ts) -- an admin can invite an admin or a member, but not an
64
- owner. The app is responsible for emailing input.email a link built from
62
+ for a role whose permissions the caller all holds -- an admin can invite
63
+ an admin or a member, but not an owner. The app is responsible for emailing input.email a link built from
65
64
  the returned invitationToken; Membership has no email column of its own.
66
65
  The invitation can be claimed until the returned invitationExpiresAt.
67
66
  Web callers must call reauthenticate() from useAuth() in onCompleted so
@@ -120,6 +120,19 @@ describe('organizations service', () => {
120
120
  },
121
121
  )
122
122
 
123
+ scenario(
124
+ 'an admin can invite an admin',
125
+ async (scenario: StandardScenario) => {
126
+ asMember(scenario, scenario.membership.admin)
127
+
128
+ const result = await inviteMember({
129
+ input: { email: 'new-admin@example.com', role: 'admin' },
130
+ })
131
+
132
+ expect(result.invitationToken).toBeTruthy()
133
+ },
134
+ )
135
+
123
136
  scenario(
124
137
  'an admin cannot invite an owner',
125
138
  async (scenario: StandardScenario) => {
@@ -6,10 +6,13 @@ import type {
6
6
  MembershipRelationResolvers,
7
7
  } from 'types/graphql'
8
8
 
9
+ import { permissions } from '__PERMISSIONS_PACKAGE__'
10
+
9
11
  import { ForbiddenError, ValidationError } from '@cedarjs/graphql-server'
10
12
  import { requireCurrentOrg } from '@cedarjs/tenancy'
11
13
 
12
14
  import { Prisma, db } from 'src/lib/db'
15
+ import { canGrantRole } from 'src/lib/permissions'
13
16
 
14
17
  /**
15
18
  * How long an invitation stays claimable after `inviteMember` creates it.
@@ -37,32 +40,6 @@ const membershipsForUser = (userId: string): Promise<MembershipSummary[]> =>
37
40
  select: { id: true, organizationId: true, role: true },
38
41
  })
39
42
 
40
- /**
41
- * Roles this generated service knows how to check, and which of them a
42
- * membership with that role may grant to someone else (`inviteMember`) or
43
- * revoke (`removeMember`). `owner` may grant or revoke any known role;
44
- * `admin` may grant or revoke `admin` and `member`, but never `owner`, so
45
- * an admin can never create or remove an owner. `member` grants nothing;
46
- * it's listed anyway so `KNOWN_ROLES` below covers every role a membership
47
- * can actually hold, which is what makes the "unknown role" check on
48
- * `inviteMember` meaningful.
49
- *
50
- * Add a role here when your app adds one. A role missing from this map can
51
- * never be granted to, or removed from, anyone by `inviteMember` /
52
- * `removeMember` -- including by an owner.
53
- */
54
- export const ROLE_GRANTS: Readonly<Record<string, readonly string[]>> = {
55
- owner: ['owner', 'admin', 'member'],
56
- admin: ['admin', 'member'],
57
- member: [],
58
- }
59
-
60
- const KNOWN_ROLES = Object.keys(ROLE_GRANTS)
61
-
62
- /** Whether a membership with `granterRole` may grant or revoke `targetRole`. */
63
- const canGrantRole = (granterRole: string, targetRole: string): boolean =>
64
- ROLE_GRANTS[granterRole]?.includes(targetRole) ?? false
65
-
66
43
  /**
67
44
  * Every membership belonging to the signed-in user, across all
68
45
  * organizations. `Membership` is a global model, so this reads plain `db`.
@@ -139,12 +116,12 @@ export const createOrganization: MutationResolvers['createOrganization'] =
139
116
 
140
117
  /**
141
118
  * Creates a pending membership (an invitation) in the current organization,
142
- * for a role the caller's own role may grant per `ROLE_GRANTS` -- an
143
- * `admin` can invite an `admin` or a `member`, but not an `owner`, even
144
- * though `@requirePermission(permission: "members.manage")` lets an admin call
145
- * this mutation at all. A role that isn't in `ROLE_GRANTS` is rejected
146
- * outright, so a typo in `input.role` can never create a membership nobody
147
- * can check against.
119
+ * for a role the caller may grant (`canGrantRole`): one whose permissions
120
+ * they all hold. An `admin` can invite an `admin` or a `member`, but not an
121
+ * `owner`, even though `@requirePermission(permission: "members.manage")`
122
+ * lets an admin call this mutation at all. A role that isn't in the
123
+ * permission definitions is rejected outright, so a typo in `input.role`
124
+ * can never create a membership with no permissions.
148
125
  *
149
126
  * `Membership` has no email column: the caller is responsible for emailing
150
127
  * `input.email` a link built from the returned `invitationToken`, with its
@@ -158,14 +135,14 @@ export const inviteMember: MutationResolvers['inviteMember'] = async ({
158
135
  }) => {
159
136
  const currentOrg = requireCurrentOrg()
160
137
 
161
- if (!KNOWN_ROLES.includes(input.role)) {
138
+ if (!Object.hasOwn(permissions.roles, input.role)) {
162
139
  throw new ValidationError(
163
- `"${input.role}" is not a known role. Add it to ROLE_GRANTS in ` +
164
- 'organizations.ts before inviting with it.',
140
+ `"${input.role}" is not a known role. Add it to your permission ` +
141
+ 'definitions before inviting with it.',
165
142
  )
166
143
  }
167
144
 
168
- if (!canGrantRole(currentOrg.role, input.role)) {
145
+ if (!canGrantRole(input.role)) {
169
146
  throw new ForbiddenError(
170
147
  `A "${currentOrg.role}" cannot invite a "${input.role}".`,
171
148
  )
@@ -288,7 +265,7 @@ export const acceptInvitation: MutationResolvers['acceptInvitation'] = async ({
288
265
  /**
289
266
  * Removes a membership from the current organization. A caller may only
290
267
  * remove a membership whose role they could grant via `inviteMember` (the
291
- * same `ROLE_GRANTS` rule), so an admin can never remove an owner, and the
268
+ * same `canGrantRole` rule), so an admin can never remove an owner, and the
292
269
  * organization's last owner can never be removed -- including by
293
270
  * themselves -- so an organization always keeps at least one owner who has
294
271
  * accepted their invitation.
@@ -306,7 +283,7 @@ export const removeMember: MutationResolvers['removeMember'] = async ({
306
283
  throw new ForbiddenError("You don't have access to that membership.")
307
284
  }
308
285
 
309
- if (!canGrantRole(currentOrg.role, membership.role)) {
286
+ if (!canGrantRole(membership.role)) {
310
287
  throw new ForbiddenError(
311
288
  `A "${currentOrg.role}" cannot remove a "${membership.role}" member.`,
312
289
  )
@@ -9,12 +9,14 @@ import { createPermissions } from '@cedarjs/tenancy'
9
9
  * `useAuth().hasPermission()`. The web only decides what to show; these
10
10
  * checks are the guard.
11
11
  *
12
- * Who may grant which role is `ROLE_GRANTS` in
13
- * api/src/services/organizations/organizations.
12
+ * `canGrantRole` is who may give someone a role, or take it away: whoever
13
+ * holds every permission it has (`inviteMember` and `removeMember` in
14
+ * api/src/services/organizations).
14
15
  */
15
16
  export const {
16
17
  hasPermission,
17
18
  requirePermission,
19
+ canGrantRole,
18
20
  permissionsOf,
19
21
  withPermissions,
20
22
  } = createPermissions(permissions)
@@ -2,12 +2,18 @@ import { definePermissions } from '@cedarjs/tenancy/permissions'
2
2
  import type { PermissionName } from '@cedarjs/tenancy/permissions'
3
3
 
4
4
  /**
5
- * Every permission the app checks, and what each role may do. Fields ask
6
- * for a permission (`@requirePermission(permission: "members.manage")`, or
7
- * `requirePermission()` in a service) instead of listing roles, so a new
8
- * role is one entry here: it can do exactly what its list says, and a
5
+ * Every permission the app checks, and what each role may do: a role is a
6
+ * named bundle of permissions. Fields ask for a permission
7
+ * (`@requirePermission(permission: "members.manage")`, or
8
+ * `requirePermission('members.manage')` in a service), never a role, so a
9
+ * new role is one entry here: it can do exactly what its list says, and a
9
10
  * field added later is closed to it unless its permission is on the list.
10
11
  *
12
+ * `organization.read` is "any member": every role lists it, and fields any
13
+ * member may use ask for it. `organization.manage` is the owner's alone,
14
+ * which also keeps admins from making someone an owner: a role can only be
15
+ * given by someone who holds every permission it has.
16
+ *
11
17
  * `roles` are per organization: a membership's role decides what the user
12
18
  * may do in that organization. Fields about the user rather than an
13
19
  * organization (their own account, say) use `@requireUserAuth` instead.
@@ -17,9 +23,9 @@ import type { PermissionName } from '@cedarjs/tenancy/permissions'
17
23
  * membership's permissions from the api, through `getCurrentUser`.
18
24
  */
19
25
  export const permissions = definePermissions({
20
- permissions: ['organization.read', 'members.manage'],
26
+ permissions: ['organization.read', 'organization.manage', 'members.manage'],
21
27
  roles: {
22
- owner: ['organization.read', 'members.manage'],
28
+ owner: ['organization.read', 'organization.manage', 'members.manage'],
23
29
  admin: ['organization.read', 'members.manage'],
24
30
  member: ['organization.read'],
25
31
  },
@@ -22,9 +22,7 @@ type RequirePermissionValidate = ValidatorDirectiveFunc<{
22
22
  const validate: RequirePermissionValidate = ({ directiveArgs }) => {
23
23
  // A name the permissions map doesn't know throws, so a typo in the SDL is
24
24
  // an error rather than a field nobody can use
25
- applicationRequirePermission({
26
- permission: directiveArgs.permission as Permission,
27
- })
25
+ applicationRequirePermission(directiveArgs.permission as Permission)
28
26
  }
29
27
 
30
28
  const requirePermission = createValidatorDirective(schema, validate)
@@ -16,12 +16,8 @@ import { createTenancy } from '@cedarjs/tenancy/web'
16
16
  *
17
17
  * A user who isn't signed in is sent to log in, and one who isn't a member
18
18
  * of the organization sees `notAMember`. Pages only some members may open
19
- * take `orgRoles` or `orgPermission` (see api/src/lib/permissions), and the
20
- * one a member without them is sent to is `unauthorized`:
21
- *
22
- * <PrivateSet unauthenticated="login" unauthorized="projects" orgRoles="owner">
23
- * <Route path="/org/{orgSlug}/settings" page={SettingsPage} name="settings" />
24
- * </PrivateSet>
19
+ * take `orgPermission` (see packages/permissions), and the one a member
20
+ * without it is sent to is `unauthorized`:
25
21
  *
26
22
  * <PrivateSet unauthenticated="login" unauthorized="projects" orgPermission="members.manage">
27
23
  * <Route path="/org/{orgSlug}/members" page={MembersPage} name="members" />
@@ -271,7 +271,7 @@ ${SIGNUP_SNIPPET}`;
271
271
  skip: () => isDbAuthProject ? false : "Not a dbAuth project; skipping."
272
272
  },
273
273
  {
274
- title: "Switching @requireAuth to @requireOrgAuth and @requireUserAuth, and hasRole to hasUserRole...",
274
+ title: "Switching @requireAuth to @requireUserAuth, and hasRole to hasUserRole...",
275
275
  task: async (ctx) => {
276
276
  const oldDirectiveDir = path.join(paths.api.directives, "requireAuth");
277
277
  const isInOldDirective = (file) => file.startsWith(oldDirectiveDir + path.sep);
@@ -308,17 +308,18 @@ ${SIGNUP_SNIPPET}`;
308
308
  } else if (fs.existsSync(oldDirectivePath)) {
309
309
  fs.rmSync(oldDirectiveDir, { recursive: true, force: true });
310
310
  }
311
- for (const name of ["requireOrgAuth", "requireUserAuth"]) {
312
- const directiveDir = path.join(paths.api.directives, name);
313
- await writeTemplateFile(
314
- `${name}.ts.template`,
315
- path.join(directiveDir, `${name}.${ext}`)
316
- );
317
- await writeTemplateFile(
318
- `${name}.test.ts.template`,
319
- path.join(directiveDir, `${name}.test.${ext}`)
320
- );
321
- }
311
+ const directiveDir = path.join(
312
+ paths.api.directives,
313
+ "requireUserAuth"
314
+ );
315
+ await writeTemplateFile(
316
+ "requireUserAuth.ts.template",
317
+ path.join(directiveDir, `requireUserAuth.${ext}`)
318
+ );
319
+ await writeTemplateFile(
320
+ "requireUserAuth.test.ts.template",
321
+ path.join(directiveDir, `requireUserAuth.test.${ext}`)
322
+ );
322
323
  }
323
324
  },
324
325
  {
@@ -375,7 +376,8 @@ ${SIGNUP_SNIPPET}`;
375
376
  const withUserFields = (firstNumber) => (content) => addRequiredUserFields(content, userFields, { firstNumber });
376
377
  await writeTemplateFile(
377
378
  "organizations.ts.template",
378
- path.join(serviceDir, `organizations.${ext}`)
379
+ path.join(serviceDir, `organizations.${ext}`),
380
+ { __PERMISSIONS_PACKAGE__: permissionsPackageName() }
379
381
  );
380
382
  await writeTemplateFile(
381
383
  "organizations.test.ts.template",
@@ -536,7 +538,8 @@ ${SIGNUP_SNIPPET}`;
536
538
  Contact) whose rows belong to one organization, add
537
539
  ${c.highlight("organizationId String")} plus ${c.highlight("@@index([organizationId])")} to it in
538
540
  schema.prisma, and switch its fields from ${c.highlight("@requireUserAuth")}
539
- to ${c.highlight("@requireOrgAuth")}. For each one that stays global, add it
541
+ to ${c.highlight('@requirePermission(permission: "organization.read")')}, or
542
+ a permission of its own. For each one that stays global, add it
540
543
  to the ${c.highlight("allExcept")} list in api/src/lib/db instead.
541
544
  3. Put your organization routes in a PrivateSet in web/src/Routes.tsx.
542
545
  The Router renders the organization for it, using the generated
@@ -567,29 +570,30 @@ ${ctx.routerNeedsTenancy ? `
567
570
  )}.
568
571
  5. Your permissions, and what each role may do, are in
569
572
  ${c.highlight("packages/permissions")}, shared by the api and web sides${ctx.packagesWorkspaceTurnedOn ? ` (this
570
- turned on ${c.highlight("experimental.packagesWorkspace")} in cedar.toml)` : ""}. Guard
571
- organization fields with ${c.highlight('@requirePermission(permission: "...")')}
572
- (or ${c.highlight("@requireOrgAuth(roles: [...])")}), and check permissions on the
573
- web with ${c.highlight("useAuth().hasPermission()")}.
573
+ turned on ${c.highlight("experimental.packagesWorkspace")} in cedar.toml)` : ""}. A role is a bundle of
574
+ permissions: guard organization fields with
575
+ ${c.highlight('@requirePermission(permission: "...")')}, never a role, and check
576
+ permissions on the web with ${c.highlight("useAuth().hasPermission()")}. Fields
577
+ any member may use ask for ${c.highlight("organization.read")}, which every
578
+ role lists.
574
579
  6. ${c.highlight("@requireAuth")} is gone. Your fields use ${c.highlight("@requireUserAuth")} now,
575
580
  which checks what ${c.highlight("@requireAuth")} did: there were no organizations
576
- before. ${c.highlight("@requireOrgAuth")} needs a current organization, and checks
577
- the user's role in it.${ctx.keptRequireAuthDirective ? `
581
+ before. ${c.highlight("@requirePermission")} needs a current organization, and
582
+ checks what the user's role there may do.${ctx.keptRequireAuthDirective ? `
578
583
  api/src/directives/requireAuth was changed from Cedar's, so
579
584
  it and your @requireAuth fields were left as they are: move
580
- its checks to requireUserAuth or requireOrgAuth, switch the
585
+ its checks to requireUserAuth or requirePermission, switch the
581
586
  fields over, then delete it.` : ""}
582
587
  7. Every role check names its kind now. ${c.highlight("hasRole")} is
583
588
  ${c.highlight("hasUserRole")} on both sides (your existing uses were renamed),
584
- checking ${c.highlight("currentUser.roles")}; ${c.highlight("hasOrgRole")} checks the role in
585
- the organization. ${c.highlight("<PrivateSet roles>")} isn't available with
589
+ checking ${c.highlight("currentUser.roles")}. In the organization, check
590
+ permissions instead. ${c.highlight("<PrivateSet roles>")} isn't available with
586
591
  tenancy: guard routes with ${c.highlight("<PrivateSet userRoles>")} for
587
- ${c.highlight("currentUser.roles")}, and with ${c.highlight("<PrivateSet orgRoles>")} for the
588
- role in the route's organization. A member who lacks it is sent
589
- to ${c.highlight("unauthorized")}, and ${c.highlight("<PrivateSet orgPermission>")} checks a
590
- permission of their membership the same way:
592
+ ${c.highlight("currentUser.roles")}, and with ${c.highlight("<PrivateSet orgPermission>")} for a
593
+ permission of their membership in the route's organization. A
594
+ member who lacks it is sent to ${c.highlight("unauthorized")}:
591
595
 
592
- <PrivateSet unauthenticated="login" unauthorized="projects" orgRoles="owner">
596
+ <PrivateSet unauthenticated="login" unauthorized="projects" orgPermission="organization.manage">
593
597
  <Route path="/org/{orgSlug}/settings" page={SettingsPage} name="settings" />
594
598
  </PrivateSet>
595
599
 
@@ -2,8 +2,9 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { getPaths } from "./index.js";
4
4
  function hasTenancyAuthDirectives() {
5
- return fs.existsSync(path.join(getPaths().api.directives, "requireOrgAuth"));
5
+ return fs.existsSync(path.join(getPaths().api.directives, "requireUserAuth"));
6
6
  }
7
+ const MEMBER_PERMISSION = "organization.read";
7
8
  function tenantField() {
8
9
  for (const ext of ["ts", "js"]) {
9
10
  const dbPath = path.join(getPaths().api.lib, `db.${ext}`);
@@ -26,10 +27,11 @@ function withAuthDirective(source, { belongsToOrg }) {
26
27
  }
27
28
  return source.replace(
28
29
  /@requireAuth(?![\w$])/g,
29
- belongsToOrg ? "@requireOrgAuth" : "@requireUserAuth"
30
+ belongsToOrg ? `@requirePermission(permission: "${MEMBER_PERMISSION}")` : "@requireUserAuth"
30
31
  );
31
32
  }
32
33
  export {
34
+ MEMBER_PERMISSION,
33
35
  belongsToOrganization,
34
36
  hasTenancyAuthDirectives,
35
37
  tenantField,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "8.0.0-canary.3565",
3
+ "version": "8.0.0-canary.3567",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,17 +36,17 @@
36
36
  "@babel/preset-typescript": "7.29.7",
37
37
  "@babel/traverse": "7.29.10",
38
38
  "@babel/types": "7.29.11",
39
- "@cedarjs/api-server": "8.0.0-canary.3565",
40
- "@cedarjs/babel-config": "8.0.0-canary.3565",
41
- "@cedarjs/cli-helpers": "8.0.0-canary.3565",
42
- "@cedarjs/internal": "8.0.0-canary.3565",
43
- "@cedarjs/prerender": "8.0.0-canary.3565",
44
- "@cedarjs/project-config": "8.0.0-canary.3565",
45
- "@cedarjs/structure": "8.0.0-canary.3565",
46
- "@cedarjs/telemetry": "8.0.0-canary.3565",
47
- "@cedarjs/utils": "8.0.0-canary.3565",
48
- "@cedarjs/vite": "8.0.0-canary.3565",
49
- "@cedarjs/web-server": "8.0.0-canary.3565",
39
+ "@cedarjs/api-server": "8.0.0-canary.3567",
40
+ "@cedarjs/babel-config": "8.0.0-canary.3567",
41
+ "@cedarjs/cli-helpers": "8.0.0-canary.3567",
42
+ "@cedarjs/internal": "8.0.0-canary.3567",
43
+ "@cedarjs/prerender": "8.0.0-canary.3567",
44
+ "@cedarjs/project-config": "8.0.0-canary.3567",
45
+ "@cedarjs/structure": "8.0.0-canary.3567",
46
+ "@cedarjs/telemetry": "8.0.0-canary.3567",
47
+ "@cedarjs/utils": "8.0.0-canary.3567",
48
+ "@cedarjs/vite": "8.0.0-canary.3567",
49
+ "@cedarjs/web-server": "8.0.0-canary.3567",
50
50
  "@listr2/prompt-adapter-enquirer": "4.4.3",
51
51
  "@opentelemetry/api": "1.9.1",
52
52
  "@opentelemetry/core": "2.12.0",
@@ -93,7 +93,7 @@
93
93
  "yargs": "18.2.0"
94
94
  },
95
95
  "devDependencies": {
96
- "@cedarjs/framework-tools": "8.0.0-canary.3565",
96
+ "@cedarjs/framework-tools": "8.0.0-canary.3567",
97
97
  "@prisma/dmmf": "7.10.1",
98
98
  "@types/archiver": "^7.0.0",
99
99
  "memfs": "4.80.0",
@@ -1,56 +0,0 @@
1
- import { mockRedwoodDirective, getDirectiveName } from '@cedarjs/testing/api'
2
-
3
- import requireOrgAuth from './requireOrgAuth.js'
4
-
5
- const currentOrg = (role: string) => ({
6
- id: 'org-1',
7
- slug: 'org-1',
8
- role,
9
- membershipId: 'membership-1',
10
- })
11
-
12
- describe('requireOrgAuth directive', () => {
13
- it('declares the directive sdl as schema, with the correct name', () => {
14
- expect(requireOrgAuth.schema).toBeTruthy()
15
- expect(getDirectiveName(requireOrgAuth.schema)).toBe('requireOrgAuth')
16
- })
17
-
18
- it('throws when there is no current user', () => {
19
- const mockExecution = mockRedwoodDirective(requireOrgAuth, { context: {} })
20
-
21
- expect(mockExecution).toThrowError("You don't have permission to do that.")
22
- })
23
-
24
- it('throws when there is no current organization', () => {
25
- const mockExecution = mockRedwoodDirective(requireOrgAuth, {
26
- context: { currentUser: { id: 'user-1' } },
27
- })
28
-
29
- expect(mockExecution).toThrowError(
30
- 'You must be a member of an organization to do that.',
31
- )
32
- })
33
-
34
- it('does not throw for a member of the current organization', () => {
35
- const mockExecution = mockRedwoodDirective(requireOrgAuth, {
36
- context: {
37
- currentUser: { id: 'user-1' },
38
- currentOrg: currentOrg('member'),
39
- },
40
- })
41
-
42
- expect(mockExecution).not.toThrowError()
43
- })
44
-
45
- it("throws when the role in the current organization isn't listed", () => {
46
- const mockExecution = mockRedwoodDirective(requireOrgAuth, {
47
- context: {
48
- currentUser: { id: 'user-1' },
49
- currentOrg: currentOrg('member'),
50
- },
51
- directiveArgs: { roles: ['owner'] },
52
- })
53
-
54
- expect(mockExecution).toThrowError("You don't have access to do that.")
55
- })
56
- })
@@ -1,25 +0,0 @@
1
- import { gql } from 'graphql-tag'
2
-
3
- import type { ValidatorDirectiveFunc } from '@cedarjs/graphql-server'
4
- import { createValidatorDirective } from '@cedarjs/graphql-server'
5
-
6
- import { requireOrgAuth as applicationRequireOrgAuth } from 'src/lib/auth'
7
-
8
- export const schema = gql`
9
- """
10
- Use to check that the signed-in user is a member of the current
11
- organization, with one of an optional set of roles there.
12
- """
13
- directive @requireOrgAuth(roles: [String]) on FIELD_DEFINITION
14
- `
15
-
16
- type RequireOrgAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }>
17
-
18
- const validate: RequireOrgAuthValidate = ({ directiveArgs }) => {
19
- const { roles } = directiveArgs
20
- applicationRequireOrgAuth({ roles })
21
- }
22
-
23
- const requireOrgAuth = createValidatorDirective(schema, validate)
24
-
25
- export default requireOrgAuth