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.
- package/dist/cloudflare/src/index.ts +4 -5
- package/dist/cloudflare/wrangler.toml +1 -1
- package/dist/cloudflare-control-plane/.dev.vars.example +17 -0
- package/dist/cloudflare-control-plane/README.md +59 -0
- package/dist/cloudflare-control-plane/copy-assets.js +132 -0
- package/dist/cloudflare-control-plane/drizzle.config.ts +17 -0
- package/dist/cloudflare-control-plane/scripts/decrypt-field.mjs +33 -0
- package/dist/cloudflare-control-plane/scripts/generate-encryption-key.mjs +7 -0
- package/dist/cloudflare-control-plane/seed-helper.js +113 -0
- package/dist/cloudflare-control-plane/src/app.ts +74 -0
- package/dist/cloudflare-control-plane/src/index.ts +72 -0
- package/dist/cloudflare-control-plane/src/seed.ts +56 -0
- package/dist/cloudflare-control-plane/src/types.ts +14 -0
- package/dist/cloudflare-control-plane/tsconfig.json +14 -0
- package/dist/cloudflare-control-plane/wrangler.toml +46 -0
- package/dist/cloudflare-wfp-dispatcher/README.md +94 -0
- package/dist/cloudflare-wfp-dispatcher/src/index.ts +72 -0
- package/dist/cloudflare-wfp-dispatcher/src/types.ts +17 -0
- package/dist/cloudflare-wfp-dispatcher/tsconfig.json +14 -0
- package/dist/cloudflare-wfp-dispatcher/wrangler.toml +56 -0
- package/dist/cloudflare-wfp-tenant/.dev.vars.example +17 -0
- package/dist/cloudflare-wfp-tenant/README.md +62 -0
- package/dist/cloudflare-wfp-tenant/copy-assets.js +132 -0
- package/dist/cloudflare-wfp-tenant/drizzle.config.ts +17 -0
- package/dist/cloudflare-wfp-tenant/scripts/decrypt-field.mjs +33 -0
- package/dist/cloudflare-wfp-tenant/scripts/generate-encryption-key.mjs +7 -0
- package/dist/cloudflare-wfp-tenant/src/app.ts +37 -0
- package/dist/cloudflare-wfp-tenant/src/index.ts +69 -0
- package/dist/cloudflare-wfp-tenant/src/types.ts +16 -0
- package/dist/cloudflare-wfp-tenant/tsconfig.json +14 -0
- package/dist/cloudflare-wfp-tenant/wrangler.toml +46 -0
- package/dist/create-authhero.js +184 -37
- package/dist/proxy/src/index.ts +3 -7
- 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
|
package/dist/create-authhero.js
CHANGED
|
@@ -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 {
|
|
567
|
-
import
|
|
568
|
-
import
|
|
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
|
|
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("🛰️
|
|
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
|
|
882
|
-
|
|
883
|
-
let
|
|
884
|
-
|
|
885
|
-
let
|
|
886
|
-
n.mkdirSync(p, { recursive: !0 }), n.writeFileSync(r.join(p, "package.json"), JSON.stringify(
|
|
887
|
-
let
|
|
888
|
-
|
|
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
|
-
|
|
892
|
-
|
|
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
|
|
895
|
-
if (m === "cloudflare" && (i.githubCi === void 0 ? o || (
|
|
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) : (
|
|
901
|
-
let e = d(h,
|
|
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),
|
|
1059
|
+
if (m === "aws-sst" && _(p, h), k) {
|
|
913
1060
|
let e = {
|
|
914
|
-
alias:
|
|
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
|
|
930
|
-
console.log(`\n✅ Project "${c}" has been created with ${
|
|
931
|
-
let
|
|
932
|
-
if (
|
|
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,
|
|
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" ?
|
|
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
|
-
|
|
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
|
package/dist/proxy/src/index.ts
CHANGED
|
@@ -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
|
-
|
|
37
|
-
() => app.fetch(request),
|
|
32
|
+
return requestCtx.run({ waitUntil: ctx.waitUntil.bind(ctx) }, () =>
|
|
33
|
+
app.fetch(request),
|
|
38
34
|
);
|
|
39
35
|
},
|
|
40
36
|
};
|