create-authhero 0.45.0 → 0.47.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.
Files changed (34) hide show
  1. package/dist/cloudflare/src/index.ts +4 -5
  2. package/dist/cloudflare/wrangler.toml +1 -1
  3. package/dist/cloudflare-control-plane/.dev.vars.example +17 -0
  4. package/dist/cloudflare-control-plane/README.md +59 -0
  5. package/dist/cloudflare-control-plane/copy-assets.js +132 -0
  6. package/dist/cloudflare-control-plane/drizzle.config.ts +17 -0
  7. package/dist/cloudflare-control-plane/scripts/decrypt-field.mjs +33 -0
  8. package/dist/cloudflare-control-plane/scripts/generate-encryption-key.mjs +7 -0
  9. package/dist/cloudflare-control-plane/seed-helper.js +113 -0
  10. package/dist/cloudflare-control-plane/src/app.ts +74 -0
  11. package/dist/cloudflare-control-plane/src/index.ts +72 -0
  12. package/dist/cloudflare-control-plane/src/seed.ts +56 -0
  13. package/dist/cloudflare-control-plane/src/types.ts +14 -0
  14. package/dist/cloudflare-control-plane/tsconfig.json +14 -0
  15. package/dist/cloudflare-control-plane/wrangler.toml +46 -0
  16. package/dist/cloudflare-wfp-dispatcher/README.md +94 -0
  17. package/dist/cloudflare-wfp-dispatcher/src/index.ts +72 -0
  18. package/dist/cloudflare-wfp-dispatcher/src/types.ts +17 -0
  19. package/dist/cloudflare-wfp-dispatcher/tsconfig.json +14 -0
  20. package/dist/cloudflare-wfp-dispatcher/wrangler.toml +56 -0
  21. package/dist/cloudflare-wfp-tenant/.dev.vars.example +17 -0
  22. package/dist/cloudflare-wfp-tenant/README.md +62 -0
  23. package/dist/cloudflare-wfp-tenant/copy-assets.js +132 -0
  24. package/dist/cloudflare-wfp-tenant/drizzle.config.ts +17 -0
  25. package/dist/cloudflare-wfp-tenant/scripts/decrypt-field.mjs +33 -0
  26. package/dist/cloudflare-wfp-tenant/scripts/generate-encryption-key.mjs +7 -0
  27. package/dist/cloudflare-wfp-tenant/src/app.ts +37 -0
  28. package/dist/cloudflare-wfp-tenant/src/index.ts +69 -0
  29. package/dist/cloudflare-wfp-tenant/src/types.ts +16 -0
  30. package/dist/cloudflare-wfp-tenant/tsconfig.json +14 -0
  31. package/dist/cloudflare-wfp-tenant/wrangler.toml +46 -0
  32. package/dist/create-authhero.js +184 -37
  33. package/dist/proxy/src/index.ts +3 -7
  34. package/package.json +1 -1
@@ -0,0 +1,69 @@
1
+ import { drizzle } from "drizzle-orm/d1";
2
+ import createAdapters from "@authhero/drizzle";
3
+ import * as schema from "@authhero/drizzle/schema/sqlite";
4
+ import {
5
+ AuthHeroConfig,
6
+ DataAdapters,
7
+ createEncryptedDataAdapter,
8
+ createEncryptedDataAdapterWithKeyRing,
9
+ loadEncryptionKey,
10
+ type KeyRing,
11
+ } from "authhero";
12
+ import { withRuntimeFallback } from "@authhero/multi-tenancy";
13
+ import createApp from "./app";
14
+ import { Env } from "./types";
15
+
16
+ // The control plane tenant id whose projected defaults this tenant inherits.
17
+ // Must match the control plane Worker's CONTROL_PLANE_TENANT_ID.
18
+ const CONTROL_PLANE_TENANT_ID = "control_plane";
19
+
20
+ export default {
21
+ async fetch(request: Request, env: Env): Promise<Response> {
22
+ const url = new URL(request.url);
23
+ const issuer = `${url.protocol}//${url.host}/`;
24
+ const origin = request.headers.get("Origin") || "";
25
+
26
+ const db = drizzle(env.AUTH_DB, { schema });
27
+ let dataAdapter: DataAdapters = createAdapters(db, {
28
+ useTransactions: false,
29
+ });
30
+
31
+ // Encrypt at rest. With both keys present, this tenant's own secrets use
32
+ // ENCRYPTION_KEY while control-plane-tenant rows (the inherited Google
33
+ // secret, etc.) use the "cp" key id — readable by this Worker, but opaque
34
+ // in a raw export of AUTH_DB.
35
+ if (env.ENCRYPTION_KEY && env.CONTROL_PLANE_ENCRYPTION_KEY) {
36
+ const ring: KeyRing = {
37
+ default: await loadEncryptionKey(env.ENCRYPTION_KEY),
38
+ keys: { cp: await loadEncryptionKey(env.CONTROL_PLANE_ENCRYPTION_KEY) },
39
+ };
40
+ dataAdapter = createEncryptedDataAdapterWithKeyRing(dataAdapter, ring, {
41
+ resolveEncryptKeyId: (tenantId) =>
42
+ tenantId === CONTROL_PLANE_TENANT_ID ? "cp" : undefined,
43
+ });
44
+ } else if (env.ENCRYPTION_KEY) {
45
+ // Single-key fallback (no inherited control-plane secrets).
46
+ dataAdapter = createEncryptedDataAdapter(
47
+ dataAdapter,
48
+ await loadEncryptionKey(env.ENCRYPTION_KEY),
49
+ );
50
+ }
51
+
52
+ // Resolve inherited defaults (connections by strategy, is_system resource
53
+ // servers, inheritable hooks, email provider) from the control-plane rows
54
+ // the rollout projected into THIS tenant's database — identical read path
55
+ // to a control-plane-colocated tenant.
56
+ dataAdapter = withRuntimeFallback(dataAdapter, {
57
+ controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
58
+ });
59
+
60
+ const config: AuthHeroConfig = {
61
+ dataAdapter,
62
+ allowedOrigins: [origin].filter(Boolean),
63
+ };
64
+
65
+ const app = createApp(config);
66
+
67
+ return app.fetch(request, { ...env, ISSUER: issuer });
68
+ },
69
+ };
@@ -0,0 +1,16 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+
3
+ export interface Env {
4
+ // This tenant's own D1 database. Each WFP tenant Worker has its own.
5
+ AUTH_DB: D1Database;
6
+
7
+ // Base64-encoded 32-byte key for this tenant's own secrets at rest.
8
+ // `wrangler secret put ENCRYPTION_KEY` (per tenant Worker).
9
+ ENCRYPTION_KEY?: string;
10
+
11
+ // Base64-encoded 32-byte CONTROL PLANE key. Decrypts the shared secrets
12
+ // (e.g. Google client_secret) that the control plane projected into this
13
+ // tenant's database under the "cp" key id. The Worker holds it as a binding;
14
+ // a raw export of AUTH_DB cannot be decrypted without it.
15
+ CONTROL_PLANE_ENCRYPTION_KEY?: string;
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "types": ["@cloudflare/workers-types"]
11
+ },
12
+ "include": ["src/**/*"],
13
+ "exclude": ["node_modules"]
14
+ }
@@ -0,0 +1,46 @@
1
+ # ════════════════════════════════════════════════════════════════════════════
2
+ # AuthHero WFP Tenant Worker
3
+ # ════════════════════════════════════════════════════════════════════════════
4
+ # The full authhero app for ONE tenant, deployed into the `authhero-tenants`
5
+ # dispatch namespace and fronted by the WFP dispatcher. It reads only its own
6
+ # D1 and inherits the control plane's defaults from rows the control plane
7
+ # rollout projected into that D1.
8
+ #
9
+ # Deploy into the namespace (one per tenant):
10
+ # wrangler deploy --dispatch-namespace=authhero-tenants --name=tenant-<id>-auth
11
+ #
12
+ # Sensitive IDs (database_id) go in wrangler.local.toml (gitignored).
13
+ # ════════════════════════════════════════════════════════════════════════════
14
+
15
+ name = "tenant-auth"
16
+ main = "src/index.ts"
17
+ compatibility_date = "2026-05-01"
18
+ compatibility_flags = ["nodejs_compat"]
19
+
20
+ [assets]
21
+ directory = "./dist/assets"
22
+
23
+ # ════════════════════════════════════════════════════════════════════════════
24
+ # This tenant's own D1 database
25
+ # ════════════════════════════════════════════════════════════════════════════
26
+ # Each tenant Worker has its own database. Create one per tenant and set its id
27
+ # in wrangler.local.toml:
28
+ # npx wrangler d1 create tenant-<id>-db
29
+ [[d1_databases]]
30
+ binding = "AUTH_DB"
31
+ database_name = "tenant-db"
32
+ database_id = "local" # Use "local" for local dev, or your actual ID in wrangler.local.toml
33
+ migrations_dir = "node_modules/@authhero/drizzle/drizzle"
34
+
35
+ # ════════════════════════════════════════════════════════════════════════════
36
+ # Encryption keys (set as secrets, not here)
37
+ # ════════════════════════════════════════════════════════════════════════════
38
+ # wrangler secret put ENCRYPTION_KEY # this tenant's own key
39
+ # wrangler secret put CONTROL_PLANE_ENCRYPTION_KEY # shared "cp" key
40
+ #
41
+ # CONTROL_PLANE_ENCRYPTION_KEY must be byte-identical to the key the control
42
+ # plane used to project this tenant's inherited secrets, or they won't decrypt.
43
+
44
+ # Optional: Enable observability
45
+ # [observability]
46
+ # enabled = true
@@ -81,22 +81,19 @@ var c = new e(), l = {
81
81
  },
82
82
  dependencies: {
83
83
  "@authhero/drizzle": a,
84
- "@authhero/kysely-adapter": a,
85
84
  ...i && { "@authhero/admin": a },
86
85
  "@authhero/widget": a,
87
86
  "@hono/swagger-ui": "^0.5.0",
88
87
  "@hono/zod-openapi": "^0.19.0",
89
88
  authhero: a,
89
+ "drizzle-orm": "^0.44.0",
90
90
  hono: "^4.6.0",
91
- kysely: "latest",
92
- "kysely-d1": "latest",
93
91
  ...t && { "@authhero/multi-tenancy": a },
94
92
  ...n && { bcryptjs: "latest" }
95
93
  },
96
94
  devDependencies: {
97
95
  "@cloudflare/workers-types": "^4.0.0",
98
96
  "drizzle-kit": "^0.31.0",
99
- "drizzle-orm": "^0.44.0",
100
97
  typescript: "^5.5.0",
101
98
  wrangler: "^3.0.0"
102
99
  }
@@ -104,6 +101,127 @@ var c = new e(), l = {
104
101
  },
105
102
  seedFile: "seed.ts"
106
103
  },
104
+ "cloudflare-wfp-dispatcher": {
105
+ name: "Cloudflare Workers for Platforms — Dispatcher",
106
+ description: "Thin dispatcher worker that routes per-publisher custom domains to tenant auth workers in a dispatch namespace (pair with the `cloudflare` template for tenant workers)",
107
+ templateDir: "cloudflare-wfp-dispatcher",
108
+ packageJson: (e, t, n, r) => {
109
+ let i = r ? "workspace:*" : "latest";
110
+ return {
111
+ name: e,
112
+ version: "1.0.0",
113
+ type: "module",
114
+ scripts: {
115
+ dev: "wrangler dev --port 3001 --local-protocol https",
116
+ "dev:remote": "wrangler dev --port 3001 --local-protocol https --remote --config wrangler.local.toml",
117
+ deploy: "wrangler deploy --config wrangler.local.toml",
118
+ "db:migrate:local": "wrangler d1 migrations apply AUTH_DB --local",
119
+ "db:migrate:remote": "wrangler d1 migrations apply AUTH_DB --remote --config wrangler.local.toml",
120
+ migrate: "wrangler d1 migrations apply AUTH_DB --local",
121
+ setup: "cp wrangler.toml wrangler.local.toml && echo '✅ Created wrangler.local.toml - update with your IDs'"
122
+ },
123
+ dependencies: {
124
+ "@authhero/drizzle": i,
125
+ "@authhero/proxy": i,
126
+ "drizzle-orm": "^0.44.0",
127
+ hono: "^4.6.0"
128
+ },
129
+ devDependencies: {
130
+ "@cloudflare/workers-types": "^4.0.0",
131
+ "drizzle-kit": "^0.31.0",
132
+ typescript: "^5.5.0",
133
+ wrangler: "^3.0.0"
134
+ }
135
+ };
136
+ }
137
+ },
138
+ "cloudflare-wfp-tenant": {
139
+ name: "Cloudflare Workers for Platforms — Tenant Worker",
140
+ description: "Per-tenant authhero worker (own D1) that inherits control plane defaults via runtime fallback + keyed encryption (deploy into the dispatch namespace; pair with the control-plane template)",
141
+ templateDir: "cloudflare-wfp-tenant",
142
+ packageJson: (e, t, n, r) => {
143
+ let i = r ? "workspace:*" : "latest";
144
+ return {
145
+ name: e,
146
+ version: "1.0.0",
147
+ type: "module",
148
+ scripts: {
149
+ postinstall: "node copy-assets.js",
150
+ "copy-assets": "node copy-assets.js",
151
+ dev: "node copy-assets.js && wrangler dev --port 3002 --local-protocol https",
152
+ "dev:remote": "node copy-assets.js && wrangler dev --port 3002 --local-protocol https --remote --config wrangler.local.toml",
153
+ deploy: "node copy-assets.js && wrangler deploy --config wrangler.local.toml",
154
+ "db:migrate:local": "wrangler d1 migrations apply AUTH_DB --local",
155
+ "db:migrate:remote": "wrangler d1 migrations apply AUTH_DB --remote --config wrangler.local.toml",
156
+ migrate: "wrangler d1 migrations apply AUTH_DB --local",
157
+ setup: "cp wrangler.toml wrangler.local.toml && cp .dev.vars.example .dev.vars && echo '✅ Created wrangler.local.toml and .dev.vars - update with your IDs and CONTROL_PLANE_ENCRYPTION_KEY'",
158
+ "gen:key": "node scripts/generate-encryption-key.mjs",
159
+ decrypt: "node --env-file=.dev.vars scripts/decrypt-field.mjs"
160
+ },
161
+ dependencies: {
162
+ "@authhero/drizzle": i,
163
+ "@authhero/multi-tenancy": i,
164
+ "@authhero/widget": i,
165
+ "@hono/swagger-ui": "^0.5.0",
166
+ "@hono/zod-openapi": "^0.19.0",
167
+ authhero: i,
168
+ "drizzle-orm": "^0.44.0",
169
+ hono: "^4.6.0"
170
+ },
171
+ devDependencies: {
172
+ "@cloudflare/workers-types": "^4.0.0",
173
+ "drizzle-kit": "^0.31.0",
174
+ typescript: "^5.5.0",
175
+ wrangler: "^3.0.0"
176
+ }
177
+ };
178
+ }
179
+ },
180
+ "cloudflare-control-plane": {
181
+ name: "Cloudflare Workers for Platforms — Control Plane",
182
+ description: "Control plane worker: tenant management + rollout source that projects default connections/prompts/branding into each WFP tenant's database (pair with the dispatcher and tenant templates)",
183
+ templateDir: "cloudflare-control-plane",
184
+ packageJson: (e, t, n, r) => {
185
+ let i = r ? "workspace:*" : "latest";
186
+ return {
187
+ name: e,
188
+ version: "1.0.0",
189
+ type: "module",
190
+ scripts: {
191
+ postinstall: "node copy-assets.js",
192
+ "copy-assets": "node copy-assets.js",
193
+ dev: "node copy-assets.js && wrangler dev --port 3000 --local-protocol https",
194
+ "dev:remote": "node copy-assets.js && wrangler dev --port 3000 --local-protocol https --remote --config wrangler.local.toml",
195
+ deploy: "node copy-assets.js && wrangler deploy --config wrangler.local.toml",
196
+ "db:migrate:local": "wrangler d1 migrations apply AUTH_DB --local",
197
+ "db:migrate:remote": "wrangler d1 migrations apply AUTH_DB --remote --config wrangler.local.toml",
198
+ migrate: "wrangler d1 migrations apply AUTH_DB --local",
199
+ "seed:local": "node seed-helper.js",
200
+ "seed:remote": "node seed-helper.js '' '' remote",
201
+ seed: "node seed-helper.js",
202
+ setup: "cp wrangler.toml wrangler.local.toml && cp .dev.vars.example .dev.vars && echo '✅ Created wrangler.local.toml and .dev.vars - update with your IDs and CONTROL_PLANE_ENCRYPTION_KEY'",
203
+ "gen:key": "node scripts/generate-encryption-key.mjs",
204
+ decrypt: "node --env-file=.dev.vars scripts/decrypt-field.mjs"
205
+ },
206
+ dependencies: {
207
+ "@authhero/drizzle": i,
208
+ "@authhero/multi-tenancy": i,
209
+ "@authhero/widget": i,
210
+ "@hono/swagger-ui": "^0.5.0",
211
+ "@hono/zod-openapi": "^0.19.0",
212
+ authhero: i,
213
+ "drizzle-orm": "^0.44.0",
214
+ hono: "^4.6.0"
215
+ },
216
+ devDependencies: {
217
+ "@cloudflare/workers-types": "^4.0.0",
218
+ "drizzle-kit": "^0.31.0",
219
+ typescript: "^5.5.0",
220
+ wrangler: "^3.0.0"
221
+ }
222
+ };
223
+ }
224
+ },
107
225
  proxy: {
108
226
  name: "Proxy (Cloudflare Workers)",
109
227
  description: "Host-based reverse proxy on Cloudflare Workers — static config, no DB",
@@ -563,9 +681,9 @@ ${i}
563
681
  `;
564
682
  }
565
683
  function p(e) {
566
- return `import { D1Dialect } from "kysely-d1";
567
- import { Kysely } from "kysely";
568
- import createAdapters from "@authhero/kysely-adapter";
684
+ return `import { drizzle } from "drizzle-orm/d1";
685
+ import createAdapters from "@authhero/drizzle";
686
+ import * as schema from "@authhero/drizzle/schema/sqlite";
569
687
  import { seed, createEncryptedDataAdapter, loadEncryptionKey } from "authhero";
570
688
 
571
689
  interface Env {
@@ -582,8 +700,7 @@ export default {
582
700
  const issuer = \`\${url.protocol}//\${url.host}/\`;
583
701
 
584
702
  try {
585
- const dialect = new D1Dialect({ database: env.AUTH_DB });
586
- const db = new Kysely<any>({ dialect });
703
+ const db = drizzle(env.AUTH_DB, { schema });
587
704
  let adapters = createAdapters(db, { useTransactions: false });
588
705
 
589
706
  if (env.ENCRYPTION_KEY) {
@@ -813,9 +930,18 @@ function C() {
813
930
  console.log("\n" + "─".repeat(50)), console.log("🔐 AuthHero server running at https://localhost:3000"), console.log("🚀 Open https://localhost:3000/setup to complete initial setup"), console.log("─".repeat(50) + "\n");
814
931
  }
815
932
  function w() {
816
- console.log("\n" + "─".repeat(50)), console.log("🛰️ AuthHero proxy running at http://localhost:8787"), console.log("✏️ Edit src/proxy.config.ts to add hosts and routes"), console.log("📖 See README.md for deployment instructions"), console.log("─".repeat(50) + "\n");
933
+ console.log("\n" + "─".repeat(50)), console.log("🛰️ WFP dispatcher running at https://localhost:3001"), console.log("📦 Pair with the `cloudflare` template to deploy tenant workers:"), console.log(" wrangler deploy --dispatch-namespace=authhero-tenants --name=tenant-<id>-auth"), console.log("📖 See README.md for the full onboarding workflow"), console.log("─".repeat(50) + "\n");
817
934
  }
818
935
  function T() {
936
+ console.log("\n" + "─".repeat(50)), console.log("🏠 WFP tenant worker running at https://localhost:3002"), console.log("🔑 Set CONTROL_PLANE_ENCRYPTION_KEY in .dev.vars to match the"), console.log(" control plane, then run the control plane's sync-defaults."), console.log("📦 Deploy into the namespace:"), console.log(" wrangler deploy --dispatch-namespace=authhero-tenants --name=tenant-<id>-auth"), console.log("─".repeat(50) + "\n");
937
+ }
938
+ function E() {
939
+ console.log("\n" + "─".repeat(50)), console.log("🛰️ Control plane running at https://localhost:3000"), console.log("🚀 Open https://localhost:3000/setup to complete initial setup"), console.log("🔁 Project defaults into a tenant: POST /internal/tenants/:id/sync-defaults"), console.log(" (wire buildTenantAdapters() in src/index.ts and protect the route first)"), console.log("─".repeat(50) + "\n");
940
+ }
941
+ function D() {
942
+ console.log("\n" + "─".repeat(50)), console.log("🛰️ AuthHero proxy running at http://localhost:8787"), console.log("✏️ Edit src/proxy.config.ts to add hosts and routes"), console.log("📖 See README.md for deployment instructions"), console.log("─".repeat(50) + "\n");
943
+ }
944
+ function O() {
819
945
  console.log("\n" + "─".repeat(50)), console.log("✅ Self-signed certificates generated with openssl"), console.log("⚠️ You may need to trust the certificate in your browser"), console.log("🔐 AuthHero server running at http://localhost:3000"), console.log("📚 API documentation available at http://localhost:3000/docs"), console.log("🚀 Open http://localhost:3000/setup to complete initial setup"), console.log("─".repeat(50) + "\n");
820
946
  }
821
947
  c.version("1.0.0").description("Create a new AuthHero project").argument("[project-name]", "name of the project").option("-t, --template <type>", "template type: local, cloudflare, aws-sst, or proxy").option("--package-manager <pm>", "package manager to use: npm, yarn, pnpm, or bun").option("--multi-tenant", "enable multi-tenant mode").option("--admin-ui", "include admin UI at /admin").option("--skip-install", "skip installing dependencies").option("--skip-migrate", "skip running database migrations").option("--skip-start", "skip starting the development server").option("--github-ci", "include GitHub CI workflows with semantic versioning").option("--conformance", "add OpenID conformance suite test clients").option("--conformance-alias <alias>", "alias for conformance suite (default: authhero-local)").option("--workspace", "use workspace:* dependencies for local monorepo development").option("-y, --yes", "skip all prompts and use defaults/provided options").action(async (e, i) => {
@@ -835,9 +961,12 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
835
961
  i.template ? ([
836
962
  "local",
837
963
  "cloudflare",
964
+ "cloudflare-wfp-dispatcher",
965
+ "cloudflare-wfp-tenant",
966
+ "cloudflare-control-plane",
838
967
  "aws-sst",
839
968
  "proxy"
840
- ].includes(i.template) || (console.error(`❌ Invalid template: ${i.template}`), console.error("Valid options: local, cloudflare, aws-sst, proxy"), process.exit(1)), m = i.template, console.log(`Using template: ${l[m].name}`)) : m = (await t.prompt([{
969
+ ].includes(i.template) || (console.error(`❌ Invalid template: ${i.template}`), console.error("Valid options: local, cloudflare, cloudflare-wfp-dispatcher, cloudflare-wfp-tenant, cloudflare-control-plane, aws-sst, proxy"), process.exit(1)), m = i.template, console.log(`Using template: ${l[m].name}`)) : m = (await t.prompt([{
841
970
  type: "list",
842
971
  name: "setupType",
843
972
  message: "Select your setup type:",
@@ -852,6 +981,21 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
852
981
  value: "cloudflare",
853
982
  short: l.cloudflare.name
854
983
  },
984
+ {
985
+ name: `${l["cloudflare-wfp-dispatcher"].name}\n ${l["cloudflare-wfp-dispatcher"].description}`,
986
+ value: "cloudflare-wfp-dispatcher",
987
+ short: l["cloudflare-wfp-dispatcher"].name
988
+ },
989
+ {
990
+ name: `${l["cloudflare-control-plane"].name}\n ${l["cloudflare-control-plane"].description}`,
991
+ value: "cloudflare-control-plane",
992
+ short: l["cloudflare-control-plane"].name
993
+ },
994
+ {
995
+ name: `${l["cloudflare-wfp-tenant"].name}\n ${l["cloudflare-wfp-tenant"].description}`,
996
+ value: "cloudflare-wfp-tenant",
997
+ short: l["cloudflare-wfp-tenant"].name
998
+ },
855
999
  {
856
1000
  name: `${l["aws-sst"].name}\n ${l["aws-sst"].description}`,
857
1001
  value: "aws-sst",
@@ -865,7 +1009,7 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
865
1009
  ]
866
1010
  }])).setupType;
867
1011
  let h;
868
- h = m === "proxy" ? !1 : i.multiTenant === void 0 ? o ? !1 : (await t.prompt([{
1012
+ h = m === "cloudflare-control-plane" ? !0 : m === "proxy" || m === "cloudflare-wfp-dispatcher" || m === "cloudflare-wfp-tenant" ? !1 : i.multiTenant === void 0 ? o ? !1 : (await t.prompt([{
869
1013
  type: "confirm",
870
1014
  name: "multiTenant",
871
1015
  message: "Would you like to enable multi-tenant mode?",
@@ -878,27 +1022,30 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
878
1022
  message: "Would you like to include the admin UI at /admin?",
879
1023
  default: !0
880
1024
  }])).adminUi : i.adminUi, g && console.log("Admin UI: enabled (available at /admin)"));
881
- let E = i.conformance || !1, D = i.conformanceAlias || "authhero-local";
882
- E && console.log(`OpenID Conformance Suite: enabled (alias: ${D})`);
883
- let O = i.workspace || !1;
884
- O && console.log("Workspace mode: enabled (using workspace:* dependencies)");
885
- let k = l[m];
886
- n.mkdirSync(p, { recursive: !0 }), n.writeFileSync(r.join(p, "package.json"), JSON.stringify(k.packageJson(c, h, E, O, g), null, 2));
887
- let A = k.templateDir, j = r.dirname(a(import.meta.url)), M = [r.join(j, A), r.join(j, "..", "templates", A)], N = M.find((e) => n.existsSync(e));
888
- if (N ? u(N, p) : (console.error(`❌ Template directory not found. Looked in:\n ${M.join("\n ")}`), process.exit(1)), m === "cloudflare" && S(p, h, g), m === "cloudflare") {
1025
+ let k = i.conformance || !1, A = i.conformanceAlias || "authhero-local";
1026
+ k && console.log(`OpenID Conformance Suite: enabled (alias: ${A})`);
1027
+ let j = i.workspace || !1;
1028
+ j && console.log("Workspace mode: enabled (using workspace:* dependencies)");
1029
+ let M = l[m];
1030
+ n.mkdirSync(p, { recursive: !0 }), n.writeFileSync(r.join(p, "package.json"), JSON.stringify(M.packageJson(c, h, k, j, g), null, 2));
1031
+ let N = M.templateDir, P = r.dirname(a(import.meta.url)), F = [r.join(P, N), r.join(P, "..", "templates", N)], I = F.find((e) => n.existsSync(e));
1032
+ I ? u(I, p) : (console.error(`❌ Template directory not found. Looked in:\n ${F.join("\n ")}`), process.exit(1)), m === "cloudflare" && S(p, h, g);
1033
+ let L = m === "cloudflare" || m === "cloudflare-wfp-dispatcher" || m === "cloudflare-wfp-tenant" || m === "cloudflare-control-plane", R = m === "cloudflare" || m === "cloudflare-wfp-tenant" || m === "cloudflare-control-plane", z = m === "cloudflare-wfp-tenant" || m === "cloudflare-control-plane";
1034
+ if (L) {
889
1035
  let e = r.join(p, "wrangler.toml"), t = r.join(p, "wrangler.local.toml");
890
- n.existsSync(e) && n.copyFileSync(e, t);
891
- let i = r.join(p, ".dev.vars.example"), a = r.join(p, ".dev.vars");
892
- n.existsSync(i) && (n.copyFileSync(i, a), n.appendFileSync(a, `\n# Generated at-rest encryption key (local dev). Use a separate secret in production.\nENCRYPTION_KEY=${s()}\n`), console.log("🔒 Added a generated ENCRYPTION_KEY to .dev.vars")), console.log("📁 Created wrangler.local.toml and .dev.vars for local development");
1036
+ if (n.existsSync(e) && !n.existsSync(t) && n.copyFileSync(e, t), R) {
1037
+ let e = r.join(p, ".dev.vars.example"), t = r.join(p, ".dev.vars");
1038
+ n.existsSync(e) && (n.copyFileSync(e, t), n.appendFileSync(t, `\n# Generated at-rest encryption key (local dev). Use a separate secret in production.\nENCRYPTION_KEY=${s()}\n`), z ? (n.appendFileSync(t, `# Shared control-plane key id "cp". Must be byte-identical across the control plane and every tenant worker.\nCONTROL_PLANE_ENCRYPTION_KEY=${s()}\n`), console.log("🔒 Added generated ENCRYPTION_KEY + CONTROL_PLANE_ENCRYPTION_KEY to .dev.vars"), console.log("⚠️ Align CONTROL_PLANE_ENCRYPTION_KEY across the control plane and all tenant workers.")) : console.log("🔒 Added a generated ENCRYPTION_KEY to .dev.vars")), console.log("📁 Created wrangler.local.toml and .dev.vars for local development");
1039
+ } else console.log("📁 Created wrangler.local.toml for local development");
893
1040
  }
894
- let P = !1;
895
- if (m === "cloudflare" && (i.githubCi === void 0 ? o || (P = (await t.prompt([{
1041
+ let B = !1;
1042
+ if (m === "cloudflare" && (i.githubCi === void 0 ? o || (B = (await t.prompt([{
896
1043
  type: "confirm",
897
1044
  name: "includeGithubCi",
898
1045
  message: "Would you like to include GitHub CI with semantic versioning?",
899
1046
  default: !1
900
- }])).includeGithubCi) : (P = i.githubCi, P && console.log("Including GitHub CI workflows with semantic versioning")), P && (y(p), b(p))), m === "local") {
901
- let e = d(h, E, D, g);
1047
+ }])).includeGithubCi) : (B = i.githubCi, B && console.log("Including GitHub CI workflows with semantic versioning")), B && (y(p), b(p))), m === "local") {
1048
+ let e = d(h, k, A, g);
902
1049
  n.writeFileSync(r.join(p, "src/seed.ts"), e);
903
1050
  let t = f(h, g);
904
1051
  n.writeFileSync(r.join(p, "src/app.ts"), t);
@@ -909,9 +1056,9 @@ ENCRYPTION_KEY=${s()}
909
1056
  `;
910
1057
  n.writeFileSync(r.join(p, ".env"), i), console.log("🔒 Generated .env with an at-rest encryption key");
911
1058
  }
912
- if (m === "aws-sst" && _(p, h), E) {
1059
+ if (m === "aws-sst" && _(p, h), k) {
913
1060
  let e = {
914
- alias: D,
1061
+ alias: A,
915
1062
  description: "AuthHero Conformance Test",
916
1063
  server: { discoveryUrl: "http://host.docker.internal:3000/.well-known/openid-configuration" },
917
1064
  client: {
@@ -926,15 +1073,15 @@ ENCRYPTION_KEY=${s()}
926
1073
  };
927
1074
  n.writeFileSync(r.join(p, "conformance-config.json"), JSON.stringify(e, null, 2)), console.log("📝 Created conformance-config.json for OpenID Conformance Suite");
928
1075
  }
929
- let F = h ? "multi-tenant" : "single-tenant";
930
- console.log(`\n✅ Project "${c}" has been created with ${k.name} (${F}) setup!\n`);
931
- let I;
932
- if (I = i.skipInstall ? !1 : o ? !0 : (await t.prompt([{
1076
+ let V = h ? "multi-tenant" : "single-tenant";
1077
+ console.log(`\n✅ Project "${c}" has been created with ${M.name} (${V}) setup!\n`);
1078
+ let H;
1079
+ if (H = i.skipInstall ? !1 : o ? !0 : (await t.prompt([{
933
1080
  type: "confirm",
934
1081
  name: "shouldInstall",
935
1082
  message: "Would you like to install dependencies now?",
936
1083
  default: !0
937
- }])).shouldInstall, I) {
1084
+ }])).shouldInstall, H) {
938
1085
  let e;
939
1086
  i.packageManager ? ([
940
1087
  "npm",
@@ -966,7 +1113,7 @@ ENCRYPTION_KEY=${s()}
966
1113
  default: "pnpm"
967
1114
  }])).packageManager, console.log(`\n📦 Installing dependencies with ${e}...\n`);
968
1115
  try {
969
- if (await x(e === "pnpm" ? "pnpm install --ignore-workspace" : `${e} install`, p), m === "local" && (console.log("\n🔧 Building native modules...\n"), await x("npm rebuild better-sqlite3", p)), console.log("\n✅ Dependencies installed successfully!\n"), (m === "local" || m === "cloudflare") && !i.skipMigrate) {
1116
+ if (await x(e === "pnpm" ? "pnpm install --ignore-workspace" : `${e} install`, p), m === "local" && (console.log("\n🔧 Building native modules...\n"), await x("npm rebuild better-sqlite3", p)), console.log("\n✅ Dependencies installed successfully!\n"), (m === "local" || m === "cloudflare" || m === "cloudflare-wfp-tenant" || m === "cloudflare-control-plane") && !i.skipMigrate) {
970
1117
  let n;
971
1118
  n = o ? !0 : (await t.prompt([{
972
1119
  type: "confirm",
@@ -981,11 +1128,11 @@ ENCRYPTION_KEY=${s()}
981
1128
  name: "shouldStart",
982
1129
  message: "Would you like to start the development server?",
983
1130
  default: !0
984
- }])).shouldStart, n && (m === "cloudflare" ? C() : m === "aws-sst" ? v() : m === "proxy" ? w() : T(), console.log("🚀 Starting development server...\n"), await x(`${e} run dev`, p)), o && !n && (console.log("\n✅ Setup complete!"), console.log("\nTo start the development server:"), console.log(` cd ${c}`), console.log(" npm run dev"), m === "cloudflare" ? C() : m === "aws-sst" ? v() : m === "proxy" ? w() : T());
1131
+ }])).shouldStart, n && (m === "cloudflare" ? C() : m === "cloudflare-wfp-dispatcher" ? w() : m === "cloudflare-wfp-tenant" ? T() : m === "cloudflare-control-plane" ? E() : m === "aws-sst" ? v() : m === "proxy" ? D() : O(), console.log("🚀 Starting development server...\n"), await x(`${e} run dev`, p)), o && !n && (console.log("\n✅ Setup complete!"), console.log("\nTo start the development server:"), console.log(` cd ${c}`), console.log(" npm run dev"), m === "cloudflare" ? C() : m === "cloudflare-wfp-dispatcher" ? w() : m === "cloudflare-wfp-tenant" ? T() : m === "cloudflare-control-plane" ? E() : m === "aws-sst" ? v() : m === "proxy" ? D() : O());
985
1132
  } catch (e) {
986
1133
  console.error("\n❌ An error occurred:", e), process.exit(1);
987
1134
  }
988
1135
  }
989
- I || (console.log("Next steps:"), console.log(` cd ${c}`), m === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(" npm run dev"), console.log("\nOpen http://localhost:3000/setup to complete initial setup")) : m === "cloudflare" ? (console.log(" npm install"), console.log(" npm run migrate # or npm run db:migrate:remote for production"), console.log(" npm run dev # or npm run dev:remote for production"), console.log("\nOpen https://localhost:3000/setup to complete initial setup")) : m === "aws-sst" ? (console.log(" npm install"), console.log(" npm run dev # Deploys to AWS in development mode"), console.log("\nOpen your server URL /setup to complete initial setup")) : m === "proxy" && (console.log(" npm install"), console.log(" npm run dev"), console.log("\nEdit src/proxy.config.ts to add hosts and routes")), console.log(`\nServer will be available at: http://localhost:${m === "proxy" ? 8787 : 3e3}`), E && (console.log("\n🧪 OpenID Conformance Suite Testing:"), console.log(" 1. Clone and start the conformance suite (if not already running):"), console.log(" git clone https://gitlab.com/openid/conformance-suite.git"), console.log(" cd conformance-suite && mvn clean package"), console.log(" docker-compose up -d"), console.log(" 2. Open https://localhost.emobix.co.uk:8443"), console.log(" 3. Create a test plan and use conformance-config.json for settings"), console.log(` 4. Use alias: ${D}`)), console.log("\nFor more information, visit: https://authhero.net/docs\n"));
1136
+ H || (console.log("Next steps:"), console.log(` cd ${c}`), m === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(" npm run dev"), console.log("\nOpen http://localhost:3000/setup to complete initial setup")) : m === "cloudflare" ? (console.log(" npm install"), console.log(" npm run migrate # or npm run db:migrate:remote for production"), console.log(" npm run dev # or npm run dev:remote for production"), console.log("\nOpen https://localhost:3000/setup to complete initial setup")) : m === "cloudflare-wfp-dispatcher" ? (console.log(" npm install"), console.log(" npm run setup # creates wrangler.local.toml — paste your database_id"), console.log(" npx wrangler dispatch-namespace create authhero-tenants"), console.log(" npm run dev # or npm run dev:remote for production"), console.log("\nDeploy tenant workers separately (`cloudflare` template):"), console.log(" wrangler deploy --dispatch-namespace=authhero-tenants --name=tenant-<id>-auth")) : m === "cloudflare-control-plane" ? (console.log(" npm install"), console.log(" npm run setup # creates wrangler.local.toml + .dev.vars"), console.log(" # set CONTROL_PLANE_ENCRYPTION_KEY in .dev.vars (share it with every tenant worker)"), console.log(" npm run migrate"), console.log(" npm run seed"), console.log(" npm run dev # or npm run dev:remote for production"), console.log("\nWire buildTenantAdapters() in src/index.ts, then project defaults:"), console.log(" POST /internal/tenants/:id/sync-defaults")) : m === "cloudflare-wfp-tenant" ? (console.log(" npm install"), console.log(" npm run setup # creates wrangler.local.toml + .dev.vars"), console.log(" # set CONTROL_PLANE_ENCRYPTION_KEY in .dev.vars (must match the control plane)"), console.log(" npm run migrate"), console.log(" npm run dev # or npm run dev:remote for production"), console.log("\nDeploy into the dispatch namespace:"), console.log(" wrangler deploy --dispatch-namespace=authhero-tenants --name=tenant-<id>-auth")) : m === "aws-sst" ? (console.log(" npm install"), console.log(" npm run dev # Deploys to AWS in development mode"), console.log("\nOpen your server URL /setup to complete initial setup")) : m === "proxy" && (console.log(" npm install"), console.log(" npm run dev"), console.log("\nEdit src/proxy.config.ts to add hosts and routes")), console.log(`\nServer will be available at: http://localhost:${m === "proxy" ? 8787 : m === "cloudflare-wfp-dispatcher" ? 3001 : m === "cloudflare-wfp-tenant" ? 3002 : 3e3}`), k && (console.log("\n🧪 OpenID Conformance Suite Testing:"), console.log(" 1. Clone and start the conformance suite (if not already running):"), console.log(" git clone https://gitlab.com/openid/conformance-suite.git"), console.log(" cd conformance-suite && mvn clean package"), console.log(" docker-compose up -d"), console.log(" 2. Open https://localhost.emobix.co.uk:8443"), console.log(" 3. Create a test plan and use conformance-config.json for settings"), console.log(` 4. Use alias: ${A}`)), console.log("\nFor more information, visit: https://authhero.net/docs\n"));
990
1137
  }), c.parse(process.argv);
991
1138
  //#endregion
@@ -1,8 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import {
3
- createProxyApp,
4
- createStaticProxyAdapter,
5
- } from "@authhero/proxy";
2
+ import { createProxyApp, createStaticProxyAdapter } from "@authhero/proxy";
6
3
  import { proxyConfig } from "./proxy.config";
7
4
 
8
5
  // AsyncLocalStorage threads each request's ExecutionContext through to the
@@ -32,9 +29,8 @@ const app = createProxyApp({
32
29
 
33
30
  export default {
34
31
  fetch(request: Request, _env: unknown, ctx: ExecutionContext) {
35
- return requestCtx.run(
36
- { waitUntil: ctx.waitUntil.bind(ctx) },
37
- () => app.fetch(request),
32
+ return requestCtx.run({ waitUntil: ctx.waitUntil.bind(ctx) }, () =>
33
+ app.fetch(request),
38
34
  );
39
35
  },
40
36
  };
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.45.0",
8
+ "version": "0.47.0",
9
9
  "type": "module",
10
10
  "main": "dist/create-authhero.js",
11
11
  "bin": {