@cedarjs/cli 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.
Files changed (25) hide show
  1. package/dist/commands/generate/realtime/realtimeHandler.js +13 -5
  2. package/dist/commands/generate/sdl/sdlHandler.js +10 -2
  3. package/dist/commands/setup/realtime/realtimeHandler.js +43 -18
  4. package/dist/commands/setup/tenancy/authCodemod.js +181 -16
  5. package/dist/commands/setup/tenancy/authDirectives.js +101 -0
  6. package/dist/commands/setup/tenancy/permissionsPackage.js +240 -0
  7. package/dist/commands/setup/tenancy/templates/OrgScope.tsx.template +40 -1
  8. package/dist/commands/setup/tenancy/templates/organizations.sdl.ts.template +8 -6
  9. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +24 -28
  10. package/dist/commands/setup/tenancy/templates/organizations.ts.template +6 -6
  11. package/dist/commands/setup/tenancy/templates/permissions.ts.template +20 -0
  12. package/dist/commands/setup/tenancy/templates/permissionsPackage.index.ts.template +28 -0
  13. package/dist/commands/setup/tenancy/templates/requireOrgAuth.test.ts.template +56 -0
  14. package/dist/commands/setup/tenancy/templates/requireOrgAuth.ts.template +25 -0
  15. package/dist/commands/setup/tenancy/templates/requirePermission.test.ts.template +50 -0
  16. package/dist/commands/setup/tenancy/templates/requirePermission.ts.template +32 -0
  17. package/dist/commands/setup/tenancy/templates/requireUserAuth.test.ts.template +26 -0
  18. package/dist/commands/setup/tenancy/templates/requireUserAuth.ts.template +26 -0
  19. package/dist/commands/setup/tenancy/tenancyHandler.js +127 -13
  20. package/dist/commands/setup/tenancy/webHasRole.js +45 -0
  21. package/dist/commands/setup/uploads/uploadsHandler.js +4 -0
  22. package/dist/lib/authDirective.js +37 -0
  23. package/package.json +13 -13
  24. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +0 -35
  25. package/dist/commands/setup/tenancy/templates/requireMembership.ts.template +0 -25
@@ -0,0 +1,50 @@
1
+ import { mockRedwoodDirective, getDirectiveName } from '@cedarjs/testing/api'
2
+
3
+ import requirePermission from './requirePermission.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('requirePermission directive', () => {
13
+ it('declares the directive sdl as schema, with the correct name', () => {
14
+ expect(requirePermission.schema).toBeTruthy()
15
+ expect(getDirectiveName(requirePermission.schema)).toBe('requirePermission')
16
+ })
17
+
18
+ it('throws when there is no current user', () => {
19
+ const mockExecution = mockRedwoodDirective(requirePermission, {
20
+ context: {},
21
+ directiveArgs: { permission: 'organization.read' },
22
+ })
23
+
24
+ expect(mockExecution).toThrowError()
25
+ })
26
+
27
+ it("throws when the role in the current organization doesn't have it", () => {
28
+ const mockExecution = mockRedwoodDirective(requirePermission, {
29
+ context: {
30
+ currentUser: { id: 'user-1' },
31
+ currentOrg: currentOrg('member'),
32
+ },
33
+ directiveArgs: { permission: 'members.manage' },
34
+ })
35
+
36
+ expect(mockExecution).toThrowError()
37
+ })
38
+
39
+ it('passes when the role in the current organization has it', () => {
40
+ const mockExecution = mockRedwoodDirective(requirePermission, {
41
+ context: {
42
+ currentUser: { id: 'user-1' },
43
+ currentOrg: currentOrg('owner'),
44
+ },
45
+ directiveArgs: { permission: 'members.manage' },
46
+ })
47
+
48
+ expect(mockExecution).not.toThrowError()
49
+ })
50
+ })
@@ -0,0 +1,32 @@
1
+ import type { Permission } from '__PERMISSIONS_PACKAGE__'
2
+ import { gql } from 'graphql-tag'
3
+
4
+ import type { ValidatorDirectiveFunc } from '@cedarjs/graphql-server'
5
+ import { createValidatorDirective } from '@cedarjs/graphql-server'
6
+
7
+ import { requirePermission as applicationRequirePermission } from 'src/lib/permissions'
8
+
9
+ export const schema = gql`
10
+ """
11
+ Use to check that the signed-in user may do something: that their role in
12
+ the current organization has the permission (see packages/permissions).
13
+ """
14
+ directive @requirePermission(permission: String!) on FIELD_DEFINITION
15
+ `
16
+
17
+ // Optional to fit `ValidatorDirectiveFunc`; the SDL makes it required
18
+ type RequirePermissionValidate = ValidatorDirectiveFunc<{
19
+ permission?: string
20
+ }>
21
+
22
+ const validate: RequirePermissionValidate = ({ directiveArgs }) => {
23
+ // A name the permissions map doesn't know throws, so a typo in the SDL is
24
+ // an error rather than a field nobody can use
25
+ applicationRequirePermission({
26
+ permission: directiveArgs.permission as Permission,
27
+ })
28
+ }
29
+
30
+ const requirePermission = createValidatorDirective(schema, validate)
31
+
32
+ export default requirePermission
@@ -0,0 +1,26 @@
1
+ import { mockRedwoodDirective, getDirectiveName } from '@cedarjs/testing/api'
2
+
3
+ import requireUserAuth from './requireUserAuth.js'
4
+
5
+ describe('requireUserAuth directive', () => {
6
+ it('declares the directive sdl as schema, with the correct name', () => {
7
+ expect(requireUserAuth.schema).toBeTruthy()
8
+ expect(getDirectiveName(requireUserAuth.schema)).toBe('requireUserAuth')
9
+ })
10
+
11
+ it('throws when there is no current user', () => {
12
+ const mockExecution = mockRedwoodDirective(requireUserAuth, {
13
+ context: {},
14
+ })
15
+
16
+ expect(mockExecution).toThrowError("You don't have permission to do that.")
17
+ })
18
+
19
+ it('does not throw for a signed-in user, with no organization', () => {
20
+ const mockExecution = mockRedwoodDirective(requireUserAuth, {
21
+ context: { currentUser: { id: 'user-1' } },
22
+ })
23
+
24
+ expect(mockExecution).not.toThrowError()
25
+ })
26
+ })
@@ -0,0 +1,26 @@
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 { requireUserAuth as applicationRequireUserAuth } from 'src/lib/auth'
7
+
8
+ export const schema = gql`
9
+ """
10
+ Use to check whether or not a user is authenticated and is associated
11
+ with an optional set of roles, in any organization or none. For fields
12
+ about the user rather than an organization.
13
+ """
14
+ directive @requireUserAuth(roles: [String]) on FIELD_DEFINITION
15
+ `
16
+
17
+ type RequireUserAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }>
18
+
19
+ const validate: RequireUserAuthValidate = ({ directiveArgs }) => {
20
+ const { roles } = directiveArgs
21
+ applicationRequireUserAuth({ roles })
22
+ }
23
+
24
+ const requireUserAuth = createValidatorDirective(schema, validate)
25
+
26
+ export default requireUserAuth
@@ -20,6 +20,16 @@ import { errorTelemetry } from "@cedarjs/telemetry";
20
20
  import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
21
21
  import { isTypeScriptProject } from "../../../lib/project.js";
22
22
  import { runTransform } from "../../../lib/runTransform.js";
23
+ import { TENANCY_AUTH_EXPORT } from "./authCodemod.js";
24
+ import {
25
+ isDefaultRequireAuthDirective,
26
+ renameRequireAuthDirective,
27
+ renameLibAuthImports
28
+ } from "./authDirectives.js";
29
+ import {
30
+ addPermissionsPackageTasks,
31
+ permissionsPackageName
32
+ } from "./permissionsPackage.js";
23
33
  import {
24
34
  authNotSetUpMessage,
25
35
  hasWebAuthFile,
@@ -33,10 +43,15 @@ import {
33
43
  ORGANIZATION_MODEL,
34
44
  requiredUserFields
35
45
  } from "./schemaPrisma.js";
46
+ import { renameUseAuthHasRole } from "./webHasRole.js";
36
47
  const DEFAULT_TENANT_FIELD = "organizationId";
37
48
  const AUTH_SNIPPET = `
49
+ import { createTenancyAuth } from '@cedarjs/tenancy'
50
+
51
+ import { withPermissions } from 'src/lib/permissions'
52
+
38
53
  export const getCurrentUser = async (session) => {
39
- return await db.user.findUnique({
54
+ return withPermissions(await db.user.findUnique({
40
55
  where: { id: session.id },
41
56
  select: {
42
57
  // ...your existing fields...
@@ -49,11 +64,11 @@ const AUTH_SNIPPET = `
49
64
  },
50
65
  },
51
66
  },
52
- })
67
+ }))
53
68
  }
54
69
 
55
- export { hasOrgRole, requireMembership } from '@cedarjs/tenancy'
56
- `;
70
+ // In place of your hasRole and requireAuth
71
+ ${TENANCY_AUTH_EXPORT}`;
57
72
  const SIGNUP_SNIPPET = `
58
73
  handler: async ({ username, hashedPassword, salt, userAttributes }) => {
59
74
  const user = await db.user.create({
@@ -202,7 +217,7 @@ Re-run with --force to have this command append its own versions beside them ins
202
217
  }
203
218
  },
204
219
  {
205
- title: "Adding memberships to getCurrentUser and re-exporting tenancy helpers...",
220
+ title: "Adding memberships to getCurrentUser and the tenancy auth checks to api/src/lib/auth...",
206
221
  task: async (_ctx, task) => {
207
222
  if (!fs.existsSync(authPath)) {
208
223
  task.skip("No api/src/lib/auth file found; skipping.");
@@ -212,7 +227,10 @@ Re-run with --force to have this command append its own versions beside them ins
212
227
  transformPath: path.join(import.meta.dirname, "authCodemod.js"),
213
228
  targetPaths: [authPath]
214
229
  });
215
- if (transformResult.error) {
230
+ if (transformResult.error?.includes("AUTH_CHECKS_CHANGED")) {
231
+ task.output = `api/src/lib/auth's requireAuth or hasRole has checks of its own, so it was left as it is. Please add this by hand, keeping your checks:
232
+ ${AUTH_SNIPPET}`;
233
+ } else if (transformResult.error) {
216
234
  task.output = `Could not update api/src/lib/auth automatically. Please add this by hand:
217
235
  ${AUTH_SNIPPET}`;
218
236
  }
@@ -253,19 +271,94 @@ ${SIGNUP_SNIPPET}`;
253
271
  skip: () => isDbAuthProject ? false : "Not a dbAuth project; skipping."
254
272
  },
255
273
  {
256
- title: "Adding the requireMembership directive...",
274
+ title: "Switching @requireAuth to @requireOrgAuth and @requireUserAuth, and hasRole to hasUserRole...",
275
+ task: async (ctx) => {
276
+ const oldDirectiveDir = path.join(paths.api.directives, "requireAuth");
277
+ const isInOldDirective = (file) => file.startsWith(oldDirectiveDir + path.sep);
278
+ const oldDirectivePath = path.join(
279
+ oldDirectiveDir,
280
+ `requireAuth.${ext}`
281
+ );
282
+ const keepOldDirective = fs.existsSync(oldDirectivePath) && !isDefaultRequireAuthDirective(
283
+ fs.readFileSync(oldDirectivePath, "utf-8")
284
+ );
285
+ const sourceFilesIn = (dir) => fs.readdirSync(dir, { recursive: true, withFileTypes: true }).filter(
286
+ (entry) => entry.isFile() && /\.[jt]sx?$/.test(entry.name)
287
+ ).map((entry) => path.join(entry.parentPath, entry.name));
288
+ for (const file of sourceFilesIn(paths.api.src)) {
289
+ const source = fs.readFileSync(file, "utf-8");
290
+ const renamed = renameLibAuthImports(
291
+ keepOldDirective || isInOldDirective(file) ? source : renameRequireAuthDirective(source)
292
+ );
293
+ if (renamed !== source) {
294
+ fs.writeFileSync(file, renamed);
295
+ writtenFiles.push(file);
296
+ }
297
+ }
298
+ for (const file of sourceFilesIn(paths.web.src)) {
299
+ const source = fs.readFileSync(file, "utf-8");
300
+ const renamed = renameUseAuthHasRole(source);
301
+ if (renamed !== source) {
302
+ fs.writeFileSync(file, renamed);
303
+ writtenFiles.push(file);
304
+ }
305
+ }
306
+ if (keepOldDirective) {
307
+ ctx.keptRequireAuthDirective = true;
308
+ } else if (fs.existsSync(oldDirectivePath)) {
309
+ fs.rmSync(oldDirectiveDir, { recursive: true, force: true });
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
+ }
322
+ }
323
+ },
324
+ {
325
+ title: "Adding the permissions package...",
326
+ task: () => addPermissionsPackageTasks({
327
+ cedarVersion,
328
+ indexSource: fs.readFileSync(
329
+ path.resolve(
330
+ import.meta.dirname,
331
+ "templates",
332
+ "permissionsPackage.index.ts.template"
333
+ ),
334
+ "utf-8"
335
+ ),
336
+ force
337
+ })
338
+ },
339
+ {
340
+ title: "Adding permission checks and the requirePermission directive...",
257
341
  task: async () => {
342
+ const replacements = {
343
+ __PERMISSIONS_PACKAGE__: permissionsPackageName()
344
+ };
345
+ await writeTemplateFile(
346
+ "permissions.ts.template",
347
+ path.join(paths.api.lib, `permissions.${ext}`),
348
+ replacements
349
+ );
258
350
  const directiveDir = path.join(
259
351
  paths.api.directives,
260
- "requireMembership"
352
+ "requirePermission"
261
353
  );
262
354
  await writeTemplateFile(
263
- "requireMembership.ts.template",
264
- path.join(directiveDir, `requireMembership.${ext}`)
355
+ "requirePermission.ts.template",
356
+ path.join(directiveDir, `requirePermission.${ext}`),
357
+ replacements
265
358
  );
266
359
  await writeTemplateFile(
267
- "requireMembership.test.ts.template",
268
- path.join(directiveDir, `requireMembership.test.${ext}`)
360
+ "requirePermission.test.ts.template",
361
+ path.join(directiveDir, `requirePermission.test.${ext}`)
269
362
  );
270
363
  }
271
364
  },
@@ -424,7 +517,8 @@ ${SIGNUP_SNIPPET}`;
424
517
  User/Organization/Membership. For each existing model (e.g.
425
518
  Contact) whose rows belong to one organization, add
426
519
  ${c.highlight("organizationId String")} plus ${c.highlight("@@index([organizationId])")} to it in
427
- schema.prisma; for each one that stays global, add it
520
+ schema.prisma, and switch its fields from ${c.highlight("@requireUserAuth")}
521
+ to ${c.highlight("@requireOrgAuth")}. For each one that stays global, add it
428
522
  to the ${c.highlight("allExcept")} list in api/src/lib/db instead.
429
523
  3. Wrap your organization routes with the generated OrgScope in
430
524
  web/src/Routes.tsx:
@@ -441,6 +535,26 @@ ${SIGNUP_SNIPPET}`;
441
535
  tenant-owned models with ${c.highlight("withTenancy")} from ${c.highlight(
442
536
  "@cedarjs/tenancy"
443
537
  )}.
538
+ 5. Your permissions, and what each role may do, are in
539
+ ${c.highlight("packages/permissions")}, shared by the api and web sides${ctx.packagesWorkspaceTurnedOn ? ` (this
540
+ turned on ${c.highlight("experimental.packagesWorkspace")} in cedar.toml)` : ""}. Guard
541
+ organization fields with ${c.highlight('@requirePermission(permission: "...")')}
542
+ (or ${c.highlight("@requireOrgAuth(roles: [...])")}), and check permissions on the
543
+ web with ${c.highlight("useAuth().hasPermission()")}.
544
+ 6. ${c.highlight("@requireAuth")} is gone. Your fields use ${c.highlight("@requireUserAuth")} now,
545
+ which checks what ${c.highlight("@requireAuth")} did: there were no organizations
546
+ before. ${c.highlight("@requireOrgAuth")} needs a current organization, and checks
547
+ the user's role in it.${ctx.keptRequireAuthDirective ? `
548
+ api/src/directives/requireAuth was changed from Cedar's, so
549
+ it and your @requireAuth fields were left as they are: move
550
+ its checks to requireUserAuth or requireOrgAuth, switch the
551
+ fields over, then delete it.` : ""}
552
+ 6. Every role check names its kind now. ${c.highlight("hasRole")} is
553
+ ${c.highlight("hasUserRole")} on both sides (your existing uses were renamed),
554
+ checking ${c.highlight("currentUser.roles")}; ${c.highlight("hasOrgRole")} checks the role in
555
+ the organization. ${c.highlight("<PrivateSet roles>")} isn't available with
556
+ tenancy: guard routes by role with ${c.highlight("OrgScope")}'s ${c.highlight("roles")} and
557
+ ${c.highlight("unauthorized")}, from a small wrapper component.
444
558
 
445
559
  ${keptFiles.length > 0 ? `
446
560
  Kept these existing files rather than overwriting them (run with
@@ -0,0 +1,45 @@
1
+ const HAS_ROLE = /(?<![\w$])hasRole(?![\w$])/;
2
+ const USE_AUTH_DESTRUCTURING = /\{([^{}]*)\}(\s*=\s*useAuth\(\))/g;
3
+ const USE_AUTH_VARIABLE = /(?:const|let|var)\s+([\w$]+)\s*=\s*useAuth\(\)/g;
4
+ const HAS_ROLE_ON_VARIABLE = /(?<![\w$.])([\w$]+)(\s*\??\.\s*)hasRole(?![\w$])/g;
5
+ function renameUseAuthHasRole(source) {
6
+ if (!source.includes("useAuth()") || !HAS_ROLE.test(source)) {
7
+ return source;
8
+ }
9
+ let renameUses = false;
10
+ let result = source.replace(
11
+ /(useAuth\(\)\s*\??\.\s*)hasRole(?![\w$])/g,
12
+ "$1hasUserRole"
13
+ );
14
+ result = result.replace(
15
+ USE_AUTH_DESTRUCTURING,
16
+ (_statement, specifiers, rest) => {
17
+ const renamed = specifiers.replace(
18
+ /(?<![\w$])hasRole(?![\w$])(\s*:)?/g,
19
+ (_specifier, alias) => {
20
+ if (!alias) {
21
+ renameUses = true;
22
+ }
23
+ return `hasUserRole${alias ?? ""}`;
24
+ }
25
+ );
26
+ return `{${renamed}}${rest}`;
27
+ }
28
+ );
29
+ if (renameUses) {
30
+ result = result.replace(/(?<![\w$.])hasRole(?![\w$])/g, "hasUserRole");
31
+ }
32
+ const authVariables = new Set(
33
+ Array.from(source.matchAll(USE_AUTH_VARIABLE), ([, name]) => name)
34
+ );
35
+ if (authVariables.size === 0) {
36
+ return result;
37
+ }
38
+ return result.replace(
39
+ HAS_ROLE_ON_VARIABLE,
40
+ (match, name, access) => authVariables.has(name) ? `${name}${access}hasUserRole` : match
41
+ );
42
+ }
43
+ export {
44
+ renameUseAuthHasRole
45
+ };
@@ -14,6 +14,7 @@ import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display"
14
14
  import { addEnvVar } from "@cedarjs/cli-helpers/project";
15
15
  import { getSchemaPath } from "@cedarjs/project-config";
16
16
  import { errorTelemetry } from "@cedarjs/telemetry";
17
+ import { withAuthDirective } from "../../../lib/authDirective.js";
17
18
  import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
18
19
  import { isTypeScriptProject } from "../../../lib/project.js";
19
20
  import { generateSecret } from "../../generate/secret/secret.js";
@@ -112,6 +113,9 @@ const handler = async ({ targets, force }) => {
112
113
  for (const [token, value] of Object.entries(replacements)) {
113
114
  templateContent = templateContent.replaceAll(token, value);
114
115
  }
116
+ templateContent = withAuthDirective(templateContent, {
117
+ belongsToOrg: false
118
+ });
115
119
  const content = projectIsTypescript ? templateContent : await transformTSToJS(outputPath, templateContent);
116
120
  return writeFile(outputPath, content, { overwriteExisting: force });
117
121
  };
@@ -0,0 +1,37 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getPaths } from "./index.js";
4
+ function hasTenancyAuthDirectives() {
5
+ return fs.existsSync(path.join(getPaths().api.directives, "requireOrgAuth"));
6
+ }
7
+ function tenantField() {
8
+ for (const ext of ["ts", "js"]) {
9
+ const dbPath = path.join(getPaths().api.lib, `db.${ext}`);
10
+ if (fs.existsSync(dbPath)) {
11
+ const match = fs.readFileSync(dbPath, "utf-8").match(/\btenantField:\s*['"](\w+)['"]/);
12
+ return match?.[1] ?? "organizationId";
13
+ }
14
+ }
15
+ return "organizationId";
16
+ }
17
+ function belongsToOrganization(model) {
18
+ const field = tenantField();
19
+ return model.fields.some(
20
+ ({ name, type }) => name === field || type === "Organization"
21
+ );
22
+ }
23
+ function withAuthDirective(source, { belongsToOrg }) {
24
+ if (!hasTenancyAuthDirectives()) {
25
+ return source;
26
+ }
27
+ return source.replace(
28
+ /@requireAuth(?![\w$])/g,
29
+ belongsToOrg ? "@requireOrgAuth" : "@requireUserAuth"
30
+ );
31
+ }
32
+ export {
33
+ belongsToOrganization,
34
+ hasTenancyAuthDirectives,
35
+ tenantField,
36
+ withAuthDirective
37
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "8.0.0-canary.3538",
3
+ "version": "8.0.0-canary.3540",
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.3538",
40
- "@cedarjs/babel-config": "8.0.0-canary.3538",
41
- "@cedarjs/cli-helpers": "8.0.0-canary.3538",
42
- "@cedarjs/internal": "8.0.0-canary.3538",
43
- "@cedarjs/prerender": "8.0.0-canary.3538",
44
- "@cedarjs/project-config": "8.0.0-canary.3538",
45
- "@cedarjs/structure": "8.0.0-canary.3538",
46
- "@cedarjs/telemetry": "8.0.0-canary.3538",
47
- "@cedarjs/utils": "8.0.0-canary.3538",
48
- "@cedarjs/vite": "8.0.0-canary.3538",
49
- "@cedarjs/web-server": "8.0.0-canary.3538",
39
+ "@cedarjs/api-server": "8.0.0-canary.3540",
40
+ "@cedarjs/babel-config": "8.0.0-canary.3540",
41
+ "@cedarjs/cli-helpers": "8.0.0-canary.3540",
42
+ "@cedarjs/internal": "8.0.0-canary.3540",
43
+ "@cedarjs/prerender": "8.0.0-canary.3540",
44
+ "@cedarjs/project-config": "8.0.0-canary.3540",
45
+ "@cedarjs/structure": "8.0.0-canary.3540",
46
+ "@cedarjs/telemetry": "8.0.0-canary.3540",
47
+ "@cedarjs/utils": "8.0.0-canary.3540",
48
+ "@cedarjs/vite": "8.0.0-canary.3540",
49
+ "@cedarjs/web-server": "8.0.0-canary.3540",
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.3538",
96
+ "@cedarjs/framework-tools": "8.0.0-canary.3540",
97
97
  "@prisma/dmmf": "7.10.1",
98
98
  "@types/archiver": "^7.0.0",
99
99
  "memfs": "4.80.0",
@@ -1,35 +0,0 @@
1
- import { mockRedwoodDirective, getDirectiveName } from '@cedarjs/testing/api'
2
-
3
- import requireMembership from './requireMembership.js'
4
-
5
- /**
6
- * `context.currentUser` is typed from this app's `getCurrentUser`, which
7
- * selects more fields than these tests need.
8
- */
9
- const asCurrentUser = (user: { id: string; memberships?: unknown[] }) =>
10
- user as unknown as NonNullable<typeof context.currentUser>
11
-
12
- describe('requireMembership directive', () => {
13
- it('declares the directive sdl as schema, with the correct name', () => {
14
- expect(requireMembership.schema).toBeTruthy()
15
- expect(getDirectiveName(requireMembership.schema)).toBe(
16
- 'requireMembership',
17
- )
18
- })
19
-
20
- it('throws when there is no current user', () => {
21
- const mockExecution = mockRedwoodDirective(requireMembership, {
22
- context: {},
23
- })
24
-
25
- expect(mockExecution).toThrowError()
26
- })
27
-
28
- it('throws when there is a current user but no current organization', () => {
29
- const mockExecution = mockRedwoodDirective(requireMembership, {
30
- context: { currentUser: asCurrentUser({ id: '1' }) },
31
- })
32
-
33
- expect(mockExecution).toThrowError()
34
- })
35
- })
@@ -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 { requireMembership as applicationRequireMembership } from 'src/lib/auth'
7
-
8
- export const schema = gql`
9
- """
10
- Use to check whether or not the current user is a member of the current
11
- organization, optionally restricted to a set of roles.
12
- """
13
- directive @requireMembership(roles: [String]) on FIELD_DEFINITION
14
- `
15
-
16
- type RequireMembershipValidate = ValidatorDirectiveFunc<{ roles?: string[] }>
17
-
18
- const validate: RequireMembershipValidate = ({ directiveArgs }) => {
19
- const { roles } = directiveArgs
20
- applicationRequireMembership({ roles })
21
- }
22
-
23
- const requireMembership = createValidatorDirective(schema, validate)
24
-
25
- export default requireMembership