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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,6 @@ import { addApiPackages } from "@cedarjs/cli-helpers/installHelpers";
7
7
  import { runBinSync } from "@cedarjs/cli-helpers/packageManager/exec";
8
8
  import { generate as generateTypes } from "@cedarjs/internal/dist/generate/generate";
9
9
  import { errorTelemetry } from "@cedarjs/telemetry";
10
- import { withAuthDirective } from "../../../lib/authDirective.js";
11
10
  import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
12
11
  import { isTypeScriptProject, serverFileExists } from "../../../lib/project.js";
13
12
  import { setupServerFileTasks } from "../server-file/serverFileHandler.js";
@@ -98,13 +97,9 @@ async function handler(args) {
98
97
  exampleSubscriptionTemplateContent
99
98
  );
100
99
  return [
101
- writeFile(
102
- exampleFile,
103
- withAuthDirective(setupScriptContent, { belongsToOrg: false }),
104
- {
105
- overwriteExisting: force
106
- }
107
- )
100
+ writeFile(exampleFile, setupScriptContent, {
101
+ overwriteExisting: force
102
+ })
108
103
  ];
109
104
  }
110
105
  },
@@ -163,23 +158,15 @@ async function handler(args) {
163
158
  exampleSubscriptionTemplateContent
164
159
  );
165
160
  return [
166
- writeFile(
167
- sdlFile,
168
- withAuthDirective(sdlContent, { belongsToOrg: false }),
169
- {
170
- overwriteExisting: force
171
- }
172
- ),
161
+ writeFile(sdlFile, sdlContent, {
162
+ overwriteExisting: force
163
+ }),
173
164
  writeFile(serviceFile, serviceContent, {
174
165
  overwriteExisting: force
175
166
  }),
176
- writeFile(
177
- exampleFile,
178
- withAuthDirective(setupScriptContent, { belongsToOrg: false }),
179
- {
180
- overwriteExisting: force
181
- }
182
- )
167
+ writeFile(exampleFile, setupScriptContent, {
168
+ overwriteExisting: force
169
+ })
183
170
  ];
184
171
  }
185
172
  },
@@ -219,13 +206,9 @@ async function handler(args) {
219
206
  );
220
207
  const serviceContent = ts ? exampleServiceTemplateContent : await transformTSToJS(serviceFile, exampleServiceTemplateContent);
221
208
  return [
222
- writeFile(
223
- sdlFile,
224
- withAuthDirective(sdlContent, { belongsToOrg: false }),
225
- {
226
- overwriteExisting: force
227
- }
228
- ),
209
+ writeFile(sdlFile, sdlContent, {
210
+ overwriteExisting: force
211
+ }),
229
212
  writeFile(serviceFile, serviceContent, {
230
213
  overwriteExisting: force
231
214
  })
@@ -268,13 +251,9 @@ async function handler(args) {
268
251
  );
269
252
  const serviceContent = ts ? exampleServiceTemplateContent : await transformTSToJS(serviceFile, exampleServiceTemplateContent);
270
253
  return [
271
- writeFile(
272
- sdlFile,
273
- withAuthDirective(sdlContent, { belongsToOrg: false }),
274
- {
275
- overwriteExisting: force
276
- }
277
- ),
254
+ writeFile(sdlFile, sdlContent, {
255
+ overwriteExisting: force
256
+ }),
278
257
  writeFile(serviceFile, serviceContent, {
279
258
  overwriteExisting: force
280
259
  })
@@ -317,13 +296,9 @@ async function handler(args) {
317
296
  );
318
297
  const serviceContent = ts ? exampleServiceTemplateContent : await transformTSToJS(serviceFile, exampleServiceTemplateContent);
319
298
  return [
320
- writeFile(
321
- sdlFile,
322
- withAuthDirective(sdlContent, { belongsToOrg: false }),
323
- {
324
- overwriteExisting: force
325
- }
326
- ),
299
+ writeFile(sdlFile, sdlContent, {
300
+ overwriteExisting: force
301
+ }),
327
302
  writeFile(serviceFile, serviceContent, {
328
303
  overwriteExisting: force
329
304
  })
@@ -18,58 +18,6 @@ function buildMembershipsSelectProperty() {
18
18
  }
19
19
  return property;
20
20
  }
21
- const TENANCY_AUTH_EXPORT = `export const { hasUserRole, requireUserAuth } = createTenancyAuth()
22
- `;
23
- const DEFAULT_REQUIRE_AUTH_STATEMENTS = [
24
- /^if \(!isAuthenticated\(\)\) \{ throw new AuthenticationError\([^)]*\);? \}$/,
25
- /^if \(roles && !hasRole\(roles\)\) \{ throw new ForbiddenError\([^)]*\);? \}$/,
26
- /^return isAuthenticated\(\);?$/
27
- ];
28
- const DEFAULT_HAS_ROLE_NAMES = /* @__PURE__ */ new Set([
29
- "AllowedRoles",
30
- "Array",
31
- "allowedRole",
32
- "allowedRoles",
33
- "context",
34
- "currentUser",
35
- "currentUserRoles",
36
- "hasRole",
37
- "includes",
38
- "isArray",
39
- "isAuthenticated",
40
- "role",
41
- "roles",
42
- "some",
43
- "undefined"
44
- ]);
45
- function exportedValue(declaration) {
46
- const node = declaration.nodes()[0];
47
- return j.VariableDeclaration.check(node?.declaration) ? node.declaration.declarations[0] : void 0;
48
- }
49
- function isDefaultHasRole(hasRole) {
50
- return hasRole.find(j.Identifier).nodes().every((identifier) => DEFAULT_HAS_ROLE_NAMES.has(identifier.name));
51
- }
52
- function isDefaultRequireAuth(requireAuth) {
53
- const declarator = exportedValue(requireAuth);
54
- const fn = j.VariableDeclarator.check(declarator) ? declarator.init : null;
55
- if (!j.ArrowFunctionExpression.check(fn) || !j.BlockStatement.check(fn.body)) {
56
- return false;
57
- }
58
- return fn.body.body.every((statement) => {
59
- const source = j(statement).toSource().replace(/\/\/[^\n]*|\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim();
60
- return DEFAULT_REQUIRE_AUTH_STATEMENTS.some(
61
- (pattern) => pattern.test(source)
62
- );
63
- });
64
- }
65
- function findExportedConst(root, name) {
66
- return root.find(j.ExportNamedDeclaration, {
67
- declaration: {
68
- type: "VariableDeclaration",
69
- declarations: [{ id: { type: "Identifier", name } }]
70
- }
71
- });
72
- }
73
21
  const USER_LOOKUP = {
74
22
  callee: {
75
23
  type: "MemberExpression",
@@ -81,33 +29,6 @@ const USER_LOOKUP = {
81
29
  property: { name: "findUnique" }
82
30
  }
83
31
  };
84
- function addContextImport(root) {
85
- const usesContext = root.find(j.Identifier, { name: "context" }).filter(
86
- (path) => !(j.MemberExpression.check(path.parent.node) && path.parent.node.property === path.node && !path.parent.node.computed) && !(j.ObjectProperty.check(path.parent.node) && path.parent.node.key === path.node)
87
- ).length > 0;
88
- const declaresContext = root.find(j.ImportDeclaration, {
89
- source: { value: "@cedarjs/graphql-server" }
90
- }).length > 0 || root.find(j.ImportSpecifier, { local: { name: "context" } }).length > 0 || root.find(j.VariableDeclarator, { id: { name: "context" } }).length > 0;
91
- if (!usesContext || declaresContext) {
92
- return false;
93
- }
94
- const contextImport = j.withParser("ts")(`import { context } from '@cedarjs/context'
95
- `).find(j.ImportDeclaration).nodes()[0];
96
- const cedarImports = root.find(j.ImportDeclaration).filter((path) => String(path.node.source.value).startsWith("@cedarjs/"));
97
- const before = cedarImports.filter(
98
- (path) => String(path.node.source.value) > "@cedarjs/context"
99
- );
100
- if (before.length > 0) {
101
- before.at(0).insertBefore(contextImport);
102
- } else if (cedarImports.length > 0) {
103
- cedarImports.at(cedarImports.length - 1).insertAfter(contextImport);
104
- } else {
105
- root.find(j.Program).forEach((path) => {
106
- path.node.body.unshift(contextImport);
107
- });
108
- }
109
- return true;
110
- }
111
32
  function addWithPermissions(root, lookups) {
112
33
  const isWithPermissionsCall = (node) => j.CallExpression.check(node) && j.Identifier.check(node.callee) && node.callee.name === "withPermissions";
113
34
  let wrapped = false;
@@ -192,56 +113,8 @@ function transform(fileInfo) {
192
113
  if (addWithPermissions(root, membershipLookups)) {
193
114
  changed = true;
194
115
  }
195
- const alreadyWired = root.find(j.ImportSpecifier, { imported: { name: "createTenancyAuth" } }).length > 0;
196
- if (alreadyWired) {
197
- if (addContextImport(root)) {
198
- changed = true;
199
- }
200
- return addPermissionsImport(changed ? root.toSource() : fileInfo.source);
201
- }
202
- const hasRole = findExportedConst(root, "hasRole");
203
- const requireAuth = findExportedConst(root, "requireAuth");
204
- if (hasRole.length !== 1 || requireAuth.length !== 1) {
205
- throw new Error("CEDAR_CODEMOD_ERR_AUTH_CHECKS_NOT_FOUND");
206
- }
207
- if (!isDefaultRequireAuth(requireAuth) || !isDefaultHasRole(hasRole)) {
208
- throw new Error("CEDAR_CODEMOD_ERR_AUTH_CHECKS_CHANGED");
209
- }
210
- const tenancyAuth = j.withParser("ts")(TENANCY_AUTH_EXPORT).find(j.ExportNamedDeclaration).nodes()[0];
211
- requireAuth.replaceWith(tenancyAuth);
212
- hasRole.remove();
213
- root.find(j.Identifier, { name: "hasRole" }).filter(
214
- (path) => !(j.MemberExpression.check(path.parent.node) && path.parent.node.property === path.node)
215
- ).forEach((path) => {
216
- path.node.name = "hasUserRole";
217
- });
218
- const isUnused = (name) => root.find(j.Identifier, { name }).filter(
219
- (path) => !j.ImportSpecifier.check(path.parent.node) && !j.TSTypeAliasDeclaration.check(path.parent.node)
220
- ).length === 0;
221
- root.find(j.TSTypeAliasDeclaration, { id: { name: "AllowedRoles" } }).filter(() => isUnused("AllowedRoles")).remove();
222
- root.find(j.ImportDeclaration, { source: { value: "@cedarjs/graphql-server" } }).forEach((path) => {
223
- path.node.specifiers = (path.node.specifiers ?? []).filter(
224
- (specifier) => !j.ImportSpecifier.check(specifier) || !j.Identifier.check(specifier.local) || !isUnused(specifier.local.name)
225
- );
226
- }).filter((path) => (path.node.specifiers ?? []).length === 0).remove();
227
- const tenancyImport = j.withParser("ts")(`import { createTenancyAuth } from '@cedarjs/tenancy'
228
- `).find(j.ImportDeclaration).nodes()[0];
229
- const imports = root.find(j.ImportDeclaration);
230
- const cedarImports = imports.filter(
231
- (path) => String(path.node.source.value).startsWith("@cedarjs/")
232
- );
233
- const anchor = cedarImports.length > 0 ? cedarImports : imports;
234
- if (anchor.length > 0) {
235
- anchor.at(anchor.length - 1).insertAfter(tenancyImport);
236
- } else {
237
- root.find(j.Program).forEach((path) => {
238
- path.node.body.unshift(tenancyImport);
239
- });
240
- }
241
- addContextImport(root);
242
- return addPermissionsImport(root.toSource());
116
+ return addPermissionsImport(changed ? root.toSource() : fileInfo.source);
243
117
  }
244
118
  export {
245
- TENANCY_AUTH_EXPORT,
246
119
  transform as default
247
120
  };
@@ -45,7 +45,7 @@ export const schema = gql`
45
45
 
46
46
  type Query {
47
47
  "Every membership belonging to the signed-in user, across all organizations they belong to."
48
- myMemberships: [Membership!]! @requireUserAuth
48
+ myMemberships: [Membership!]! @requireAuth
49
49
 
50
50
  "The current organization, resolved by resolveCurrentOrg for this request."
51
51
  organization(id: String!): Organization
@@ -55,7 +55,7 @@ export const schema = gql`
55
55
  type Mutation {
56
56
  "Creates a new organization with the caller as its owner."
57
57
  createOrganization(input: CreateOrganizationInput!): Organization!
58
- @requireUserAuth
58
+ @requireAuth
59
59
 
60
60
  """
61
61
  Creates a pending membership (an invitation) in the current organization,
@@ -77,7 +77,7 @@ export const schema = gql`
77
77
  the new membership is reflected in currentUser.memberships.
78
78
  """
79
79
  acceptInvitation(token: String!): Organization!
80
- @requireUserAuth
80
+ @requireAuth
81
81
 
82
82
  """
83
83
  Removes a membership from the current organization, for a role the
@@ -45,7 +45,7 @@ const membershipsForUser = (userId: string): Promise<MembershipSummary[]> =>
45
45
  * organizations. `Membership` is a global model, so this reads plain `db`.
46
46
  */
47
47
  export const myMemberships: QueryResolvers['myMemberships'] = () => {
48
- // @requireUserAuth guarantees a signed-in user before this resolver runs.
48
+ // @requireAuth guarantees a signed-in user before this resolver runs.
49
49
  return db.membership.findMany({
50
50
  where: { userId: context.currentUser!.id },
51
51
  orderBy: { createdAt: 'asc' },
@@ -72,7 +72,7 @@ export const organization: QueryResolvers['organization'] = async ({ id }) => {
72
72
  export const createOrganization: MutationResolvers['createOrganization'] =
73
73
  async ({ input }) => {
74
74
  const baseSlug = slugify(input.name)
75
- // @requireUserAuth guarantees a signed-in user before this resolver runs.
75
+ // @requireAuth guarantees a signed-in user before this resolver runs.
76
76
  const userId = context.currentUser!.id
77
77
 
78
78
  let slug = baseSlug
@@ -244,7 +244,7 @@ const isExpiredInvitation = async (token: string): Promise<boolean> => {
244
244
  export const acceptInvitation: MutationResolvers['acceptInvitation'] = async ({
245
245
  token,
246
246
  }) => {
247
- // @requireUserAuth guarantees a signed-in user before this resolver runs.
247
+ // @requireAuth guarantees a signed-in user before this resolver runs.
248
248
  const userId = context.currentUser!.id
249
249
 
250
250
  const claim = await claimInvitation(token, userId)
@@ -16,7 +16,7 @@ import type { PermissionName } from '@cedarjs/tenancy/permissions'
16
16
  *
17
17
  * `roles` are per organization: a membership's role decides what the user
18
18
  * may do in that organization. Fields about the user rather than an
19
- * organization (their own account, say) use `@requireUserAuth` instead.
19
+ * organization (their own account, say) use `@requireAuth` instead.
20
20
  *
21
21
  * This package is shared: the api checks these permissions
22
22
  * (api/src/lib/permissions), and the web can import the names. The web gets each
@@ -20,12 +20,6 @@ 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
23
  import {
30
24
  addPermissionsPackageTasks,
31
25
  permissionsPackageName
@@ -43,11 +37,8 @@ import {
43
37
  ORGANIZATION_MODEL,
44
38
  requiredUserFields
45
39
  } from "./schemaPrisma.js";
46
- import { renameUseAuthHasRole } from "./webHasRole.js";
47
40
  const DEFAULT_TENANT_FIELD = "organizationId";
48
41
  const AUTH_SNIPPET = `
49
- import { createTenancyAuth } from '@cedarjs/tenancy'
50
-
51
42
  import { withPermissions } from 'src/lib/permissions'
52
43
 
53
44
  export const getCurrentUser = async (session) => {
@@ -65,10 +56,7 @@ const AUTH_SNIPPET = `
65
56
  },
66
57
  },
67
58
  }))
68
- }
69
-
70
- // In place of your hasRole and requireAuth
71
- ${TENANCY_AUTH_EXPORT}`;
59
+ }`;
72
60
  const SIGNUP_SNIPPET = `
73
61
  handler: async ({ username, hashedPassword, salt, userAttributes }) => {
74
62
  const user = await db.user.create({
@@ -217,7 +205,7 @@ Re-run with --force to have this command append its own versions beside them ins
217
205
  }
218
206
  },
219
207
  {
220
- title: "Adding memberships to getCurrentUser and the tenancy auth checks to api/src/lib/auth...",
208
+ title: "Adding memberships and their permissions to getCurrentUser...",
221
209
  task: async (_ctx, task) => {
222
210
  if (!fs.existsSync(authPath)) {
223
211
  task.skip("No api/src/lib/auth file found; skipping.");
@@ -227,10 +215,7 @@ Re-run with --force to have this command append its own versions beside them ins
227
215
  transformPath: path.join(import.meta.dirname, "authCodemod.js"),
228
216
  targetPaths: [authPath]
229
217
  });
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) {
218
+ if (transformResult.error) {
234
219
  task.output = `Could not update api/src/lib/auth automatically. Please add this by hand:
235
220
  ${AUTH_SNIPPET}`;
236
221
  }
@@ -270,58 +255,6 @@ ${SIGNUP_SNIPPET}`;
270
255
  },
271
256
  skip: () => isDbAuthProject ? false : "Not a dbAuth project; skipping."
272
257
  },
273
- {
274
- title: "Switching @requireAuth to @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
- 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
- );
323
- }
324
- },
325
258
  {
326
259
  title: "Adding the permissions package...",
327
260
  task: () => addPermissionsPackageTasks({
@@ -537,7 +470,7 @@ ${SIGNUP_SNIPPET}`;
537
470
  User/Organization/Membership. For each existing model (e.g.
538
471
  Contact) whose rows belong to one organization, add
539
472
  ${c.highlight("organizationId String")} plus ${c.highlight("@@index([organizationId])")} to it in
540
- schema.prisma, and switch its fields from ${c.highlight("@requireUserAuth")}
473
+ schema.prisma, and switch its fields from ${c.highlight("@requireAuth")}
541
474
  to ${c.highlight('@requirePermission(permission: "organization.read")')}, or
542
475
  a permission of its own. For each one that stays global, add it
543
476
  to the ${c.highlight("allExcept")} list in api/src/lib/db instead.
@@ -576,22 +509,14 @@ ${ctx.routerNeedsTenancy ? `
576
509
  permissions on the web with ${c.highlight("useAuth().hasPermission()")}. Fields
577
510
  any member may use ask for ${c.highlight("organization.read")}, which every
578
511
  role lists.
579
- 6. ${c.highlight("@requireAuth")} is gone. Your fields use ${c.highlight("@requireUserAuth")} now,
580
- which checks what ${c.highlight("@requireAuth")} did: there were no organizations
581
- before. ${c.highlight("@requirePermission")} needs a current organization, and
582
- checks what the user's role there may do.${ctx.keptRequireAuthDirective ? `
583
- api/src/directives/requireAuth was changed from Cedar's, so
584
- it and your @requireAuth fields were left as they are: move
585
- its checks to requireUserAuth or requirePermission, switch the
586
- fields over, then delete it.` : ""}
587
- 7. Every role check names its kind now. ${c.highlight("hasRole")} is
588
- ${c.highlight("hasUserRole")} on both sides (your existing uses were renamed),
589
- checking ${c.highlight("currentUser.roles")}. In the organization, check
590
- permissions instead. ${c.highlight("<PrivateSet roles>")} isn't available with
591
- tenancy: guard routes with ${c.highlight("<PrivateSet userRoles>")} for
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")}:
512
+ 6. ${c.highlight("@requireAuth")} is for fields about the signed-in user.
513
+ ${c.highlight("@requirePermission")} needs a current organization, and checks
514
+ what the user's role there may do.
515
+ 7. Roles are the user's own: ${c.highlight("hasRole")} and ${c.highlight("<PrivateSet roles>")}
516
+ check ${c.highlight("currentUser.roles")}. In an organization, check a
517
+ permission instead: guard routes with ${c.highlight("<PrivateSet orgPermission>")}
518
+ for a permission of their membership in the route's organization.
519
+ A member who lacks it is sent to ${c.highlight("unauthorized")}:
595
520
 
596
521
  <PrivateSet unauthenticated="login" unauthorized="projects" orgPermission="organization.manage">
597
522
  <Route path="/org/{orgSlug}/settings" page={SettingsPage} name="settings" />
@@ -14,7 +14,6 @@ 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";
18
17
  import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
19
18
  import { isTypeScriptProject } from "../../../lib/project.js";
20
19
  import { generateSecret } from "../../generate/secret/secret.js";
@@ -113,9 +112,6 @@ const handler = async ({ targets, force }) => {
113
112
  for (const [token, value] of Object.entries(replacements)) {
114
113
  templateContent = templateContent.replaceAll(token, value);
115
114
  }
116
- templateContent = withAuthDirective(templateContent, {
117
- belongsToOrg: false
118
- });
119
115
  const content = projectIsTypescript ? templateContent : await transformTSToJS(outputPath, templateContent);
120
116
  return writeFile(outputPath, content, { overwriteExisting: force });
121
117
  };
@@ -1,8 +1,16 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { getPaths } from "./index.js";
4
- function hasTenancyAuthDirectives() {
5
- return fs.existsSync(path.join(getPaths().api.directives, "requireUserAuth"));
4
+ function hasTenancy() {
5
+ let packageJson;
6
+ try {
7
+ packageJson = JSON.parse(
8
+ fs.readFileSync(path.join(getPaths().api.base, "package.json"), "utf-8")
9
+ );
10
+ } catch {
11
+ return false;
12
+ }
13
+ return !!packageJson.dependencies?.["@cedarjs/tenancy"] || !!packageJson.devDependencies?.["@cedarjs/tenancy"];
6
14
  }
7
15
  const MEMBER_PERMISSION = "organization.read";
8
16
  function tenantField() {
@@ -22,18 +30,18 @@ function belongsToOrganization(model) {
22
30
  );
23
31
  }
24
32
  function withAuthDirective(source, { belongsToOrg }) {
25
- if (!hasTenancyAuthDirectives()) {
33
+ if (!belongsToOrg || !hasTenancy()) {
26
34
  return source;
27
35
  }
28
36
  return source.replace(
29
37
  /@requireAuth(?![\w$])/g,
30
- belongsToOrg ? `@requirePermission(permission: "${MEMBER_PERMISSION}")` : "@requireUserAuth"
38
+ `@requirePermission(permission: "${MEMBER_PERMISSION}")`
31
39
  );
32
40
  }
33
41
  export {
34
42
  MEMBER_PERMISSION,
35
43
  belongsToOrganization,
36
- hasTenancyAuthDirectives,
44
+ hasTenancy,
37
45
  tenantField,
38
46
  withAuthDirective
39
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "8.0.0-canary.3567",
3
+ "version": "8.0.0-canary.3568",
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.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",
39
+ "@cedarjs/api-server": "8.0.0-canary.3568",
40
+ "@cedarjs/babel-config": "8.0.0-canary.3568",
41
+ "@cedarjs/cli-helpers": "8.0.0-canary.3568",
42
+ "@cedarjs/internal": "8.0.0-canary.3568",
43
+ "@cedarjs/prerender": "8.0.0-canary.3568",
44
+ "@cedarjs/project-config": "8.0.0-canary.3568",
45
+ "@cedarjs/structure": "8.0.0-canary.3568",
46
+ "@cedarjs/telemetry": "8.0.0-canary.3568",
47
+ "@cedarjs/utils": "8.0.0-canary.3568",
48
+ "@cedarjs/vite": "8.0.0-canary.3568",
49
+ "@cedarjs/web-server": "8.0.0-canary.3568",
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.3567",
96
+ "@cedarjs/framework-tools": "8.0.0-canary.3568",
97
97
  "@prisma/dmmf": "7.10.1",
98
98
  "@types/archiver": "^7.0.0",
99
99
  "memfs": "4.80.0",
@@ -1,101 +0,0 @@
1
- function renameRequireAuthDirective(source) {
2
- return source.replace(/@requireAuth(?![\w$])/g, "@requireUserAuth");
3
- }
4
- const LIB_AUTH_IMPORT = /import\s*\{([^}]*)\}\s*from\s*(['"])((?:src|\.{1,2})(?:\/[^'"]*)?\/lib\/auth(?:\.[jt]s)?)\2/g;
5
- const LIB_AUTH_RENAMES = {
6
- requireAuth: "requireUserAuth",
7
- hasRole: "hasUserRole"
8
- };
9
- function renameLibAuthImports(source) {
10
- let result = source;
11
- for (const [from, to] of Object.entries(LIB_AUTH_RENAMES)) {
12
- let renameUses = false;
13
- result = result.replace(
14
- LIB_AUTH_IMPORT,
15
- (statement, specifiers) => {
16
- const renamed = specifiers.replace(
17
- new RegExp(`(?<![\\w$])${from}(?![\\w$])(\\s+as\\s+[\\w$]+)?`, "g"),
18
- (_specifier, alias) => {
19
- if (!alias) {
20
- renameUses = true;
21
- }
22
- return `${to}${alias ?? ""}`;
23
- }
24
- );
25
- return statement.replace(specifiers, renamed);
26
- }
27
- );
28
- if (renameUses) {
29
- result = result.replace(
30
- new RegExp(`(?<![\\w$@.])${from}(?![\\w$])`, "g"),
31
- to
32
- );
33
- }
34
- }
35
- return result;
36
- }
37
- const DEFAULT_REQUIRE_AUTH_DIRECTIVES = [
38
- `import { gql } from 'graphql-tag'
39
-
40
- import type { ValidatorDirectiveFunc } from '@cedarjs/graphql-server'
41
- import { createValidatorDirective } from '@cedarjs/graphql-server'
42
-
43
- import { requireAuth as applicationRequireAuth } from 'src/lib/auth'
44
-
45
- export const schema = gql\`
46
- """
47
- Use to check whether or not a user is authenticated and is associated
48
- with an optional set of roles.
49
- """
50
- directive @requireAuth(roles: [String]) on FIELD_DEFINITION
51
- \`
52
-
53
- type RequireAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }>
54
-
55
- const validate: RequireAuthValidate = ({ directiveArgs }) => {
56
- const { roles } = directiveArgs
57
- applicationRequireAuth({ roles })
58
- }
59
-
60
- const requireAuth = createValidatorDirective(schema, validate)
61
-
62
- export default requireAuth
63
- `,
64
- `import { gql } from 'graphql-tag'
65
-
66
- import { createValidatorDirective } from '@cedarjs/graphql-server'
67
-
68
- import { requireAuth as applicationRequireAuth } from 'src/lib/auth'
69
-
70
- export const schema = gql\`
71
- """
72
- Use to check whether or not a user is authenticated and is associated
73
- with an optional set of roles.
74
- """
75
- directive @requireAuth(roles: [String]) on FIELD_DEFINITION
76
- \`
77
-
78
- const validate = ({ directiveArgs }) => {
79
- const { roles } = directiveArgs
80
- applicationRequireAuth({ roles })
81
- }
82
-
83
- const requireAuth = createValidatorDirective(schema, validate)
84
-
85
- export default requireAuth
86
- `
87
- ];
88
- function withoutFormatting(source) {
89
- return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "").replace(/["']/g, "'").replace(/[;\s]/g, "").replace(/,([)\]}>])/g, "$1");
90
- }
91
- function isDefaultRequireAuthDirective(source) {
92
- return DEFAULT_REQUIRE_AUTH_DIRECTIVES.some(
93
- (directive) => withoutFormatting(directive) === withoutFormatting(source)
94
- );
95
- }
96
- export {
97
- DEFAULT_REQUIRE_AUTH_DIRECTIVES,
98
- isDefaultRequireAuthDirective,
99
- renameLibAuthImports,
100
- renameRequireAuthDirective
101
- };
@@ -1,26 +0,0 @@
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
- })
@@ -1,26 +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 { 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
@@ -1,45 +0,0 @@
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
- };