create-authhero 0.18.0 → 0.19.0

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.
@@ -1,29 +1,65 @@
1
1
  import { Context } from "hono";
2
2
  import { HTTPException } from "hono/http-exception";
3
3
  import { swaggerUI } from "@hono/swagger-ui";
4
+ import { init, AuthHeroConfig, fetchAll } from "authhero";
4
5
  import {
5
- init,
6
- MultiTenantAuthHeroConfig,
7
- DataAdapters,
6
+ createSyncHooks,
7
+ createTenantsOpenAPIRouter,
8
+ createProtectSyncedMiddleware,
8
9
  } from "@authhero/multi-tenancy";
10
+ import { DataAdapters } from "@authhero/adapter-interfaces";
9
11
 
10
12
  // Control plane tenant ID - the tenant that manages all other tenants
11
13
  const CONTROL_PLANE_TENANT_ID = "control_plane";
12
14
 
13
- export default function createApp(
14
- config: Omit<MultiTenantAuthHeroConfig, "controlPlaneTenantId"> & {
15
- dataAdapter: DataAdapters;
16
- },
17
- ) {
15
+ export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAdapters }) {
16
+ // Create sync hooks for syncing entities from control plane to child tenants
17
+ const { entityHooks, tenantHooks } = createSyncHooks({
18
+ controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
19
+ getChildTenantIds: async () => {
20
+ const allTenants = await fetchAll<{ id: string }>(
21
+ (params) => config.dataAdapter.tenants.list(params),
22
+ "tenants",
23
+ { cursorField: "id", pageSize: 100 },
24
+ );
25
+ return allTenants
26
+ .filter((t) => t.id !== CONTROL_PLANE_TENANT_ID)
27
+ .map((t) => t.id);
28
+ },
29
+ getAdapters: async () => config.dataAdapter,
30
+ getControlPlaneAdapters: async () => config.dataAdapter,
31
+ sync: {
32
+ resourceServers: true,
33
+ roles: true,
34
+ connections: true,
35
+ },
36
+ });
37
+
38
+ // Create tenants router
39
+ const tenantsRouter = createTenantsOpenAPIRouter(
40
+ {
41
+ accessControl: {
42
+ controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
43
+ requireOrganizationMatch: false,
44
+ defaultPermissions: ["tenant:admin"],
45
+ },
46
+ },
47
+ { tenants: tenantHooks },
48
+ );
49
+
50
+ // Initialize AuthHero with sync hooks and tenant routes
18
51
  const { app } = init({
19
52
  ...config,
20
- controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
21
- // Sync resource servers from control plane tenant to all child tenants
22
- syncResourceServers: true,
23
- // Sync roles from control plane tenant to all child tenants
24
- syncRoles: true,
53
+ entityHooks,
54
+ managementApiExtensions: [
55
+ ...(config.managementApiExtensions || []),
56
+ { path: "/tenants", router: tenantsRouter },
57
+ ],
25
58
  });
26
59
 
60
+ // Add middleware to protect synced entities from modification on child tenants
61
+ app.use("/api/v2/*", createProtectSyncedMiddleware());
62
+
27
63
  app
28
64
  .onError((err, ctx) => {
29
65
  if (err instanceof HTTPException) {
@@ -3,7 +3,7 @@ import { Kysely } from "kysely";
3
3
  import createAdapters from "@authhero/kysely-adapter";
4
4
  import createApp from "./app";
5
5
  import { Env } from "./types";
6
- import { AuthHeroConfig } from "@authhero/multi-tenancy";
6
+ import { AuthHeroConfig } from "authhero";
7
7
 
8
8
  // ──────────────────────────────────────────────────────────────────────────────
9
9
  // OPTIONAL: Uncomment to enable Cloudflare adapters (Analytics Engine, etc.)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command as A } from "commander";
3
3
  import m from "inquirer";
4
- import s from "fs";
4
+ import n from "fs";
5
5
  import i from "path";
6
6
  import { spawn as b } from "child_process";
7
7
  const D = new A(), c = {
@@ -106,6 +106,7 @@ const D = new A(), c = {
106
106
  "@authhero/multi-tenancy": "latest",
107
107
  "@hono/swagger-ui": "^0.5.0",
108
108
  "@hono/zod-openapi": "^0.19.0",
109
+ authhero: "latest",
109
110
  hono: "^4.6.0",
110
111
  kysely: "latest",
111
112
  "kysely-d1": "latest"
@@ -122,9 +123,9 @@ const D = new A(), c = {
122
123
  }
123
124
  };
124
125
  function j(o, e) {
125
- s.readdirSync(o).forEach((n) => {
126
- const a = i.join(o, n), t = i.join(e, n);
127
- s.lstatSync(a).isDirectory() ? (s.mkdirSync(t, { recursive: !0 }), j(a, t)) : s.copyFileSync(a, t);
126
+ n.readdirSync(o).forEach((s) => {
127
+ const a = i.join(o, s), t = i.join(e, s);
128
+ n.lstatSync(a).isDirectory() ? (n.mkdirSync(t, { recursive: !0 }), j(a, t)) : n.copyFileSync(a, t);
128
129
  });
129
130
  }
130
131
  function I() {
@@ -163,7 +164,7 @@ main().catch(console.error);
163
164
  }
164
165
  function x(o) {
165
166
  const e = i.join(o, ".github", "workflows");
166
- s.mkdirSync(e, { recursive: !0 });
167
+ n.mkdirSync(e, { recursive: !0 });
167
168
  const r = `name: Unit tests
168
169
 
169
170
  on: push
@@ -185,7 +186,7 @@ jobs:
185
186
 
186
187
  - run: npm run type-check
187
188
  - run: npm test
188
- `, n = `name: Deploy to Dev
189
+ `, s = `name: Deploy to Dev
189
190
 
190
191
  on:
191
192
  push:
@@ -250,7 +251,7 @@ jobs:
250
251
  apiToken: \${{ secrets.PROD_CLOUDFLARE_API_TOKEN }}
251
252
  command: deploy --env production
252
253
  `;
253
- s.writeFileSync(i.join(e, "unit-tests.yml"), r), s.writeFileSync(i.join(e, "deploy-dev.yml"), n), s.writeFileSync(i.join(e, "release.yml"), a), console.log("\\n📦 GitHub CI workflows created!");
254
+ n.writeFileSync(i.join(e, "unit-tests.yml"), r), n.writeFileSync(i.join(e, "deploy-dev.yml"), s), n.writeFileSync(i.join(e, "release.yml"), a), console.log("\\n📦 GitHub CI workflows created!");
254
255
  }
255
256
  function C(o) {
256
257
  const e = {
@@ -261,34 +262,34 @@ function C(o) {
261
262
  "@semantic-release/github"
262
263
  ]
263
264
  };
264
- s.writeFileSync(
265
+ n.writeFileSync(
265
266
  i.join(o, ".releaserc.json"),
266
267
  JSON.stringify(e, null, 2)
267
268
  );
268
- const r = i.join(o, "package.json"), n = JSON.parse(s.readFileSync(r, "utf-8"));
269
- n.devDependencies = {
270
- ...n.devDependencies,
269
+ const r = i.join(o, "package.json"), s = JSON.parse(n.readFileSync(r, "utf-8"));
270
+ s.devDependencies = {
271
+ ...s.devDependencies,
271
272
  "semantic-release": "^24.0.0"
272
- }, n.scripts = {
273
- ...n.scripts,
273
+ }, s.scripts = {
274
+ ...s.scripts,
274
275
  test: 'echo "No tests yet"',
275
276
  "type-check": "tsc --noEmit"
276
- }, s.writeFileSync(r, JSON.stringify(n, null, 2));
277
+ }, n.writeFileSync(r, JSON.stringify(s, null, 2));
277
278
  }
278
279
  function v(o, e) {
279
- return new Promise((r, n) => {
280
+ return new Promise((r, s) => {
280
281
  const a = b(o, [], {
281
282
  cwd: e,
282
283
  shell: !0,
283
284
  stdio: "inherit"
284
285
  });
285
286
  a.on("close", (t) => {
286
- t === 0 ? r() : n(new Error(`Command failed with exit code ${t}`));
287
- }), a.on("error", n);
287
+ t === 0 ? r() : s(new Error(`Command failed with exit code ${t}`));
288
+ }), a.on("error", s);
288
289
  });
289
290
  }
290
291
  function S(o, e, r) {
291
- return new Promise((n, a) => {
292
+ return new Promise((s, a) => {
292
293
  const t = b(o, [], {
293
294
  cwd: e,
294
295
  shell: !0,
@@ -296,7 +297,7 @@ function S(o, e, r) {
296
297
  env: { ...process.env, ...r }
297
298
  });
298
299
  t.on("close", (f) => {
299
- f === 0 ? n() : a(new Error(`Command failed with exit code ${f}`));
300
+ f === 0 ? s() : a(new Error(`Command failed with exit code ${f}`));
300
301
  }), t.on("error", a);
301
302
  });
302
303
  }
@@ -311,8 +312,8 @@ D.version("1.0.0").description("Create a new AuthHero project").argument("[proje
311
312
  console.log(`
312
313
  🔐 Welcome to AuthHero!
313
314
  `);
314
- let n = o;
315
- n || (r ? (n = "auth-server", console.log(`Using default project name: ${n}`)) : n = (await m.prompt([
315
+ let s = o;
316
+ s || (r ? (s = "auth-server", console.log(`Using default project name: ${s}`)) : s = (await m.prompt([
316
317
  {
317
318
  type: "input",
318
319
  name: "projectName",
@@ -321,8 +322,8 @@ D.version("1.0.0").description("Create a new AuthHero project").argument("[proje
321
322
  validate: (p) => p !== "" || "Project name cannot be empty"
322
323
  }
323
324
  ])).projectName);
324
- const a = i.join(process.cwd(), n);
325
- s.existsSync(a) && (console.error(`❌ Project "${n}" already exists.`), process.exit(1));
325
+ const a = i.join(process.cwd(), s);
326
+ n.existsSync(a) && (console.error(`❌ Project "${s}" already exists.`), process.exit(1));
326
327
  let t;
327
328
  e.template ? (["local", "cloudflare-simple", "cloudflare-multitenant"].includes(
328
329
  e.template
@@ -356,19 +357,19 @@ D.version("1.0.0").description("Create a new AuthHero project").argument("[proje
356
357
  }
357
358
  ])).setupType;
358
359
  const f = c[t];
359
- s.mkdirSync(a, { recursive: !0 }), s.writeFileSync(
360
+ n.mkdirSync(a, { recursive: !0 }), n.writeFileSync(
360
361
  i.join(a, "package.json"),
361
- JSON.stringify(f.packageJson(n), null, 2)
362
+ JSON.stringify(f.packageJson(s), null, 2)
362
363
  );
363
364
  const k = i.join(
364
365
  import.meta.url.replace("file://", "").replace("/create-authhero.js", ""),
365
366
  f.templateDir
366
367
  );
367
- if (s.existsSync(k) ? j(k, a) : (console.error(`❌ Template directory not found: ${k}`), process.exit(1)), t === "cloudflare-simple" || t === "cloudflare-multitenant") {
368
+ if (n.existsSync(k) ? j(k, a) : (console.error(`❌ Template directory not found: ${k}`), process.exit(1)), t === "cloudflare-simple" || t === "cloudflare-multitenant") {
368
369
  const l = i.join(a, "wrangler.toml"), p = i.join(a, "wrangler.local.toml");
369
- s.existsSync(l) && s.copyFileSync(l, p);
370
+ n.existsSync(l) && n.copyFileSync(l, p);
370
371
  const d = i.join(a, ".dev.vars.example"), u = i.join(a, ".dev.vars");
371
- s.existsSync(d) && s.copyFileSync(d, u), console.log(
372
+ n.existsSync(d) && n.copyFileSync(d, u), console.log(
372
373
  "📁 Created wrangler.local.toml and .dev.vars for local development"
373
374
  );
374
375
  }
@@ -382,11 +383,11 @@ D.version("1.0.0").description("Create a new AuthHero project").argument("[proje
382
383
  }
383
384
  ])).includeGithubCi), y && (x(a), C(a))), t === "local") {
384
385
  const l = I();
385
- s.writeFileSync(i.join(a, "src/seed.ts"), l);
386
+ n.writeFileSync(i.join(a, "src/seed.ts"), l);
386
387
  }
387
388
  console.log(
388
389
  `
389
- ✅ Project "${n}" has been created with ${f.name} setup!
390
+ ✅ Project "${s}" has been created with ${f.name} setup!
390
391
  `
391
392
  );
392
393
  let h;
@@ -487,7 +488,7 @@ D.version("1.0.0").description("Create a new AuthHero project").argument("[proje
487
488
  `
488
489
  ), await v(`${l} run dev`, a)), r && !d && (console.log(`
489
490
  ✅ Setup complete!`), console.log(`
490
- To start the development server:`), console.log(` cd ${n}`), console.log(" npm run dev"), (t === "cloudflare-simple" || t === "cloudflare-multitenant") && console.log(
491
+ To start the development server:`), console.log(` cd ${s}`), console.log(" npm run dev"), (t === "cloudflare-simple" || t === "cloudflare-multitenant") && console.log(
491
492
  `
492
493
  Server will be available at: https://localhost:3000`
493
494
  ));
@@ -496,7 +497,7 @@ Server will be available at: https://localhost:3000`
496
497
  ❌ An error occurred:`, p), process.exit(1);
497
498
  }
498
499
  }
499
- h || (console.log("Next steps:"), console.log(` cd ${n}`), t === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(
500
+ h || (console.log("Next steps:"), console.log(` cd ${s}`), t === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(
500
501
  " ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
501
502
  ), console.log(" npm run dev")) : (t === "cloudflare-simple" || t === "cloudflare-multitenant") && (console.log(" npm install"), console.log(
502
503
  " npm run migrate # or npm run db:migrate:remote for production"
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "https://github.com/markusahlstrand/authhero"
7
7
  },
8
- "version": "0.18.0",
8
+ "version": "0.19.0",
9
9
  "type": "module",
10
10
  "main": "dist/create-authhero.js",
11
11
  "bin": {