create-authhero 0.45.0 → 0.46.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-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/create-authhero.js +76 -36
- package/dist/proxy/src/index.ts +3 -7
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { drizzle } from "drizzle-orm/d1";
|
|
2
|
+
import createAdapters from "@authhero/drizzle";
|
|
3
|
+
import * as schema from "@authhero/drizzle/schema/sqlite";
|
|
4
4
|
import createApp from "./app";
|
|
5
5
|
import { Env } from "./types";
|
|
6
6
|
import {
|
|
@@ -22,8 +22,7 @@ export default {
|
|
|
22
22
|
// Get the origin from the request for dynamic CORS
|
|
23
23
|
const origin = request.headers.get("Origin") || "";
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
const db = new Kysely<any>({ dialect });
|
|
25
|
+
const db = drizzle(env.AUTH_DB, { schema });
|
|
27
26
|
let dataAdapter = createAdapters(db, { useTransactions: false });
|
|
28
27
|
|
|
29
28
|
// Encrypt sensitive credential fields at rest when ENCRYPTION_KEY is set.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# AuthHero WFP Dispatcher
|
|
2
|
+
|
|
3
|
+
Thin Cloudflare Worker that fronts a Workers-for-Platforms deployment of AuthHero. Resolves an incoming request's `Host` header to a tenant via the shared platform D1 (`custom_domains` table) and dispatches the request to that tenant's full authhero auth server, deployed as a script in a Cloudflare dispatch namespace.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Internet
|
|
9
|
+
|
|
|
10
|
+
v
|
|
11
|
+
[This worker — the dispatcher]
|
|
12
|
+
1. Host header -> custom_domains -> tenant_id
|
|
13
|
+
2. env.DISPATCHER.get('tenant-<id>-auth').fetch(request)
|
|
14
|
+
|
|
|
15
|
+
v
|
|
16
|
+
[authhero-tenants dispatch namespace]
|
|
17
|
+
|- tenant-acme-auth (full authhero, deployed from the `cloudflare` template)
|
|
18
|
+
|- tenant-bob-auth
|
|
19
|
+
|- ...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Tenant workers come from the **`cloudflare` create-authhero template** — they're the same single-tenant auth server, deployed into the namespace instead of standalone.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- A Cloudflare account on the **Workers for Platforms** plan (required for dispatch namespaces).
|
|
27
|
+
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/).
|
|
28
|
+
- A D1 database (shared with the tenant workers).
|
|
29
|
+
|
|
30
|
+
## One-time platform setup
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 1. Create the dispatch namespace
|
|
34
|
+
npx wrangler dispatch-namespace create authhero-tenants
|
|
35
|
+
|
|
36
|
+
# 2. Create the shared D1 (or reuse the one your tenant workers use)
|
|
37
|
+
npx wrangler d1 create authhero-db
|
|
38
|
+
|
|
39
|
+
# 3. Copy wrangler.toml -> wrangler.local.toml and paste the database_id
|
|
40
|
+
|
|
41
|
+
# 4. Install project dependencies (provides the local wrangler used by the
|
|
42
|
+
# db:migrate:* scripts)
|
|
43
|
+
npm install
|
|
44
|
+
|
|
45
|
+
# 5. Apply migrations to the shared D1
|
|
46
|
+
npm run db:migrate:remote
|
|
47
|
+
# Or, without installing dependencies first:
|
|
48
|
+
# npx wrangler d1 migrations apply AUTH_DB --remote --config wrangler.local.toml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Deploy the dispatcher
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm run deploy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Onboard a tenant
|
|
58
|
+
|
|
59
|
+
For each publisher:
|
|
60
|
+
|
|
61
|
+
1. **Provision the tenant in D1** — insert a `tenants` row, then a `custom_domains` row mapping their domain to the tenant_id. (Either via the authhero management API or by direct D1 query.)
|
|
62
|
+
|
|
63
|
+
2. **Deploy their auth worker into the namespace.** From the sibling `cloudflare` template:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# In the tenant's directory (scaffolded from `create-authhero --template=cloudflare`)
|
|
67
|
+
npx wrangler deploy \
|
|
68
|
+
--dispatch-namespace=authhero-tenants \
|
|
69
|
+
--name=tenant-<tenant_id>-auth
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. **Point their custom domain at this dispatcher worker** (Cloudflare → Workers → Triggers → Add Custom Domain on this worker, or via DNS to the worker's `*.workers.dev` route).
|
|
73
|
+
|
|
74
|
+
Once those three steps are done, a request to `auth.<their-domain>/authorize?...` flows to this dispatcher → resolved via custom_domains → dispatched to their tenant worker.
|
|
75
|
+
|
|
76
|
+
## Per-tenant routing customization
|
|
77
|
+
|
|
78
|
+
By default, hosts with no `proxy_routes` rows get a single catch-all that dispatches to `tenant-<tenant_id>-auth`. If a tenant needs richer routing — different middleware chains, CORS, a special path that bypasses the namespace — insert `proxy_routes` rows for that `custom_domain_id`. The dispatcher uses the configured routes verbatim instead of the default.
|
|
79
|
+
|
|
80
|
+
The script-name template (`tenant-{tenant_id}-auth`) can be overridden globally via the `SCRIPT_NAME_TEMPLATE` env var. Supported placeholders: `{tenant_id}`, `{custom_domain_id}`, `{domain}`, `{host}`.
|
|
81
|
+
|
|
82
|
+
## Local development
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm run dev
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`wrangler dev` runs against a **local SQLite-backed D1**. The dispatch namespace cannot be emulated locally — for end-to-end tests, deploy to a real Cloudflare account using `npm run dev:remote`.
|
|
89
|
+
|
|
90
|
+
## Files
|
|
91
|
+
|
|
92
|
+
- `src/index.ts` — dispatcher worker entrypoint
|
|
93
|
+
- `src/types.ts` — Env interface (D1 binding + DISPATCHER namespace binding)
|
|
94
|
+
- `wrangler.toml` — Cloudflare config (assets, D1, dispatch namespace)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { drizzle } from "drizzle-orm/d1";
|
|
2
|
+
import { createProxyDataAdapter } from "@authhero/drizzle";
|
|
3
|
+
import * as schema from "@authhero/drizzle/schema/sqlite";
|
|
4
|
+
import {
|
|
5
|
+
createProxyApp,
|
|
6
|
+
type ProxyDataAdapter,
|
|
7
|
+
type ResolvedHost,
|
|
8
|
+
} from "@authhero/proxy";
|
|
9
|
+
import type { Env } from "./types";
|
|
10
|
+
|
|
11
|
+
// `tenant-{tenant_id}-auth` is the deploy convention used by this template's
|
|
12
|
+
// per-tenant worker setup. Override with the SCRIPT_NAME_TEMPLATE env var or
|
|
13
|
+
// by configuring proxy_routes rows per tenant for richer routing.
|
|
14
|
+
const DEFAULT_SCRIPT_NAME_TEMPLATE = "tenant-{tenant_id}-auth";
|
|
15
|
+
|
|
16
|
+
// If a host resolves to a known tenant but has no proxy_routes configured,
|
|
17
|
+
// synthesize a single catch-all that dispatches to the tenant's auth worker
|
|
18
|
+
// in the namespace. Operators who need middleware (CORS, headers, rate
|
|
19
|
+
// limiting) per tenant can add real proxy_routes rows and this fallback
|
|
20
|
+
// stays out of the way.
|
|
21
|
+
function withDefaultDispatchRoute(
|
|
22
|
+
inner: ProxyDataAdapter,
|
|
23
|
+
binding: string,
|
|
24
|
+
scriptNameTemplate: string,
|
|
25
|
+
): ProxyDataAdapter {
|
|
26
|
+
return {
|
|
27
|
+
proxyRoutes: inner.proxyRoutes,
|
|
28
|
+
resolveHost: async (host): Promise<ResolvedHost | null> => {
|
|
29
|
+
const resolved = await inner.resolveHost(host);
|
|
30
|
+
if (!resolved || resolved.routes.length > 0) return resolved;
|
|
31
|
+
const now = new Date(0).toISOString();
|
|
32
|
+
return {
|
|
33
|
+
...resolved,
|
|
34
|
+
routes: [
|
|
35
|
+
{
|
|
36
|
+
id: `default-${resolved.custom_domain_id}`,
|
|
37
|
+
tenant_id: resolved.tenant_id,
|
|
38
|
+
custom_domain_id: resolved.custom_domain_id,
|
|
39
|
+
priority: 1000,
|
|
40
|
+
match: { path: "/*" },
|
|
41
|
+
handlers: [
|
|
42
|
+
{
|
|
43
|
+
type: "dispatch_namespace",
|
|
44
|
+
options: { binding, script_name: scriptNameTemplate },
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
created_at: now,
|
|
48
|
+
updated_at: now,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
58
|
+
const db = drizzle(env.AUTH_DB, { schema });
|
|
59
|
+
const data = withDefaultDispatchRoute(
|
|
60
|
+
createProxyDataAdapter(db),
|
|
61
|
+
"DISPATCHER",
|
|
62
|
+
env.SCRIPT_NAME_TEMPLATE || DEFAULT_SCRIPT_NAME_TEMPLATE,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const app = createProxyApp({
|
|
66
|
+
data,
|
|
67
|
+
bindings: { DISPATCHER: env.DISPATCHER },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return app.fetch(request);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
|
|
3
|
+
export interface Env {
|
|
4
|
+
// The shared platform D1 database. Holds custom_domains (host -> tenant)
|
|
5
|
+
// and proxy_routes (optional per-host route overrides).
|
|
6
|
+
AUTH_DB: D1Database;
|
|
7
|
+
|
|
8
|
+
// The Cloudflare Workers for Platforms dispatch namespace where each
|
|
9
|
+
// tenant's auth worker is deployed. Wrangler binding configured in
|
|
10
|
+
// wrangler.toml as `[[dispatch_namespaces]] binding = "DISPATCHER"`.
|
|
11
|
+
DISPATCHER: DispatchNamespace;
|
|
12
|
+
|
|
13
|
+
// Optional template for resolving a tenant to a dispatch namespace
|
|
14
|
+
// script name. Defaults to `tenant-{tenant_id}-auth`. Supported
|
|
15
|
+
// placeholders: {tenant_id}, {custom_domain_id}, {domain}, {host}.
|
|
16
|
+
SCRIPT_NAME_TEMPLATE?: string;
|
|
17
|
+
}
|
|
@@ -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,56 @@
|
|
|
1
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
# AuthHero WFP Dispatcher Configuration
|
|
3
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
# Thin Cloudflare Worker that resolves a request's Host header to a tenant
|
|
5
|
+
# (via the shared platform D1) and dispatches to that tenant's full
|
|
6
|
+
# authhero auth server, deployed as a script in the `authhero-tenants`
|
|
7
|
+
# dispatch namespace.
|
|
8
|
+
#
|
|
9
|
+
# Tenant workers are deployed separately via the `cloudflare` template,
|
|
10
|
+
# using `wrangler deploy --dispatch-namespace=authhero-tenants
|
|
11
|
+
# --name=tenant-<id>-auth`.
|
|
12
|
+
#
|
|
13
|
+
# Sensitive IDs (database_id) go in wrangler.local.toml (gitignored) or
|
|
14
|
+
# GitHub Secrets, not this file.
|
|
15
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
16
|
+
|
|
17
|
+
name = "authhero-wfp-dispatcher"
|
|
18
|
+
main = "src/index.ts"
|
|
19
|
+
compatibility_date = "2026-05-01"
|
|
20
|
+
compatibility_flags = ["nodejs_compat"]
|
|
21
|
+
|
|
22
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
23
|
+
# Dispatch namespace binding
|
|
24
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
25
|
+
# Create the namespace once before the first deploy:
|
|
26
|
+
# npx wrangler dispatch-namespace create authhero-tenants
|
|
27
|
+
[[dispatch_namespaces]]
|
|
28
|
+
binding = "DISPATCHER"
|
|
29
|
+
namespace = "authhero-tenants"
|
|
30
|
+
|
|
31
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
# Shared platform D1 database
|
|
33
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
# Same D1 that the tenant workers read/write. Holds custom_domains and
|
|
35
|
+
# proxy_routes that drive the dispatcher's host -> tenant resolution.
|
|
36
|
+
[[d1_databases]]
|
|
37
|
+
binding = "AUTH_DB"
|
|
38
|
+
database_name = "authhero-db"
|
|
39
|
+
database_id = "local" # Use "local" for local dev, or your actual ID in wrangler.local.toml
|
|
40
|
+
migrations_dir = "node_modules/@authhero/drizzle/drizzle"
|
|
41
|
+
|
|
42
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
43
|
+
# OPTIONAL: Custom Domain
|
|
44
|
+
# ════════════════════════════════════════════════════════════════════════════
|
|
45
|
+
# Point your platform's wildcard or per-publisher custom domains at this
|
|
46
|
+
# worker. The publishers' subdomains must resolve here so the dispatcher
|
|
47
|
+
# can route into the namespace.
|
|
48
|
+
#
|
|
49
|
+
# [[routes]]
|
|
50
|
+
# pattern = "*.auth.yourplatform.com/*"
|
|
51
|
+
# zone_name = "yourplatform.com"
|
|
52
|
+
|
|
53
|
+
# Optional: Enable observability for `wrangler tail` insight into dispatch
|
|
54
|
+
# decisions and namespace invocation latency.
|
|
55
|
+
# [observability]
|
|
56
|
+
# 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,40 @@ 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
|
+
},
|
|
107
138
|
proxy: {
|
|
108
139
|
name: "Proxy (Cloudflare Workers)",
|
|
109
140
|
description: "Host-based reverse proxy on Cloudflare Workers — static config, no DB",
|
|
@@ -563,9 +594,9 @@ ${i}
|
|
|
563
594
|
`;
|
|
564
595
|
}
|
|
565
596
|
function p(e) {
|
|
566
|
-
return `import {
|
|
567
|
-
import
|
|
568
|
-
import
|
|
597
|
+
return `import { drizzle } from "drizzle-orm/d1";
|
|
598
|
+
import createAdapters from "@authhero/drizzle";
|
|
599
|
+
import * as schema from "@authhero/drizzle/schema/sqlite";
|
|
569
600
|
import { seed, createEncryptedDataAdapter, loadEncryptionKey } from "authhero";
|
|
570
601
|
|
|
571
602
|
interface Env {
|
|
@@ -582,8 +613,7 @@ export default {
|
|
|
582
613
|
const issuer = \`\${url.protocol}//\${url.host}/\`;
|
|
583
614
|
|
|
584
615
|
try {
|
|
585
|
-
const
|
|
586
|
-
const db = new Kysely<any>({ dialect });
|
|
616
|
+
const db = drizzle(env.AUTH_DB, { schema });
|
|
587
617
|
let adapters = createAdapters(db, { useTransactions: false });
|
|
588
618
|
|
|
589
619
|
if (env.ENCRYPTION_KEY) {
|
|
@@ -813,9 +843,12 @@ function C() {
|
|
|
813
843
|
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
844
|
}
|
|
815
845
|
function w() {
|
|
816
|
-
console.log("\n" + "─".repeat(50)), console.log("🛰️
|
|
846
|
+
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
847
|
}
|
|
818
848
|
function T() {
|
|
849
|
+
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");
|
|
850
|
+
}
|
|
851
|
+
function E() {
|
|
819
852
|
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
853
|
}
|
|
821
854
|
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 +868,10 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
835
868
|
i.template ? ([
|
|
836
869
|
"local",
|
|
837
870
|
"cloudflare",
|
|
871
|
+
"cloudflare-wfp-dispatcher",
|
|
838
872
|
"aws-sst",
|
|
839
873
|
"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([{
|
|
874
|
+
].includes(i.template) || (console.error(`❌ Invalid template: ${i.template}`), console.error("Valid options: local, cloudflare, cloudflare-wfp-dispatcher, aws-sst, proxy"), process.exit(1)), m = i.template, console.log(`Using template: ${l[m].name}`)) : m = (await t.prompt([{
|
|
841
875
|
type: "list",
|
|
842
876
|
name: "setupType",
|
|
843
877
|
message: "Select your setup type:",
|
|
@@ -852,6 +886,11 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
852
886
|
value: "cloudflare",
|
|
853
887
|
short: l.cloudflare.name
|
|
854
888
|
},
|
|
889
|
+
{
|
|
890
|
+
name: `${l["cloudflare-wfp-dispatcher"].name}\n ${l["cloudflare-wfp-dispatcher"].description}`,
|
|
891
|
+
value: "cloudflare-wfp-dispatcher",
|
|
892
|
+
short: l["cloudflare-wfp-dispatcher"].name
|
|
893
|
+
},
|
|
855
894
|
{
|
|
856
895
|
name: `${l["aws-sst"].name}\n ${l["aws-sst"].description}`,
|
|
857
896
|
value: "aws-sst",
|
|
@@ -865,7 +904,7 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
865
904
|
]
|
|
866
905
|
}])).setupType;
|
|
867
906
|
let h;
|
|
868
|
-
h = m === "proxy" ? !1 : i.multiTenant === void 0 ? o ? !1 : (await t.prompt([{
|
|
907
|
+
h = m === "proxy" || m === "cloudflare-wfp-dispatcher" ? !1 : i.multiTenant === void 0 ? o ? !1 : (await t.prompt([{
|
|
869
908
|
type: "confirm",
|
|
870
909
|
name: "multiTenant",
|
|
871
910
|
message: "Would you like to enable multi-tenant mode?",
|
|
@@ -878,27 +917,28 @@ c.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
878
917
|
message: "Would you like to include the admin UI at /admin?",
|
|
879
918
|
default: !0
|
|
880
919
|
}])).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
|
-
if (
|
|
920
|
+
let D = i.conformance || !1, O = i.conformanceAlias || "authhero-local";
|
|
921
|
+
D && console.log(`OpenID Conformance Suite: enabled (alias: ${O})`);
|
|
922
|
+
let k = i.workspace || !1;
|
|
923
|
+
k && console.log("Workspace mode: enabled (using workspace:* dependencies)");
|
|
924
|
+
let A = l[m];
|
|
925
|
+
n.mkdirSync(p, { recursive: !0 }), n.writeFileSync(r.join(p, "package.json"), JSON.stringify(A.packageJson(c, h, D, k, g), null, 2));
|
|
926
|
+
let j = A.templateDir, M = r.dirname(a(import.meta.url)), N = [r.join(M, j), r.join(M, "..", "templates", j)], P = N.find((e) => n.existsSync(e));
|
|
927
|
+
if (P ? u(P, p) : (console.error(`❌ Template directory not found. Looked in:\n ${N.join("\n ")}`), process.exit(1)), m === "cloudflare" && S(p, h, g), m === "cloudflare" || m === "cloudflare-wfp-dispatcher") {
|
|
889
928
|
let e = r.join(p, "wrangler.toml"), t = r.join(p, "wrangler.local.toml");
|
|
890
|
-
n.existsSync(e) && n.copyFileSync(e, t)
|
|
891
|
-
|
|
892
|
-
|
|
929
|
+
if (n.existsSync(e) && !n.existsSync(t) && n.copyFileSync(e, t), m === "cloudflare") {
|
|
930
|
+
let e = r.join(p, ".dev.vars.example"), t = r.join(p, ".dev.vars");
|
|
931
|
+
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`), console.log("🔒 Added a generated ENCRYPTION_KEY to .dev.vars")), console.log("📁 Created wrangler.local.toml and .dev.vars for local development");
|
|
932
|
+
} else console.log("📁 Created wrangler.local.toml for local development");
|
|
893
933
|
}
|
|
894
|
-
let
|
|
895
|
-
if (m === "cloudflare" && (i.githubCi === void 0 ? o || (
|
|
934
|
+
let F = !1;
|
|
935
|
+
if (m === "cloudflare" && (i.githubCi === void 0 ? o || (F = (await t.prompt([{
|
|
896
936
|
type: "confirm",
|
|
897
937
|
name: "includeGithubCi",
|
|
898
938
|
message: "Would you like to include GitHub CI with semantic versioning?",
|
|
899
939
|
default: !1
|
|
900
|
-
}])).includeGithubCi) : (
|
|
901
|
-
let e = d(h,
|
|
940
|
+
}])).includeGithubCi) : (F = i.githubCi, F && console.log("Including GitHub CI workflows with semantic versioning")), F && (y(p), b(p))), m === "local") {
|
|
941
|
+
let e = d(h, D, O, g);
|
|
902
942
|
n.writeFileSync(r.join(p, "src/seed.ts"), e);
|
|
903
943
|
let t = f(h, g);
|
|
904
944
|
n.writeFileSync(r.join(p, "src/app.ts"), t);
|
|
@@ -909,9 +949,9 @@ ENCRYPTION_KEY=${s()}
|
|
|
909
949
|
`;
|
|
910
950
|
n.writeFileSync(r.join(p, ".env"), i), console.log("🔒 Generated .env with an at-rest encryption key");
|
|
911
951
|
}
|
|
912
|
-
if (m === "aws-sst" && _(p, h),
|
|
952
|
+
if (m === "aws-sst" && _(p, h), D) {
|
|
913
953
|
let e = {
|
|
914
|
-
alias:
|
|
954
|
+
alias: O,
|
|
915
955
|
description: "AuthHero Conformance Test",
|
|
916
956
|
server: { discoveryUrl: "http://host.docker.internal:3000/.well-known/openid-configuration" },
|
|
917
957
|
client: {
|
|
@@ -926,15 +966,15 @@ ENCRYPTION_KEY=${s()}
|
|
|
926
966
|
};
|
|
927
967
|
n.writeFileSync(r.join(p, "conformance-config.json"), JSON.stringify(e, null, 2)), console.log("📝 Created conformance-config.json for OpenID Conformance Suite");
|
|
928
968
|
}
|
|
929
|
-
let
|
|
930
|
-
console.log(`\n✅ Project "${c}" has been created with ${
|
|
931
|
-
let
|
|
932
|
-
if (
|
|
969
|
+
let I = h ? "multi-tenant" : "single-tenant";
|
|
970
|
+
console.log(`\n✅ Project "${c}" has been created with ${A.name} (${I}) setup!\n`);
|
|
971
|
+
let L;
|
|
972
|
+
if (L = i.skipInstall ? !1 : o ? !0 : (await t.prompt([{
|
|
933
973
|
type: "confirm",
|
|
934
974
|
name: "shouldInstall",
|
|
935
975
|
message: "Would you like to install dependencies now?",
|
|
936
976
|
default: !0
|
|
937
|
-
}])).shouldInstall,
|
|
977
|
+
}])).shouldInstall, L) {
|
|
938
978
|
let e;
|
|
939
979
|
i.packageManager ? ([
|
|
940
980
|
"npm",
|
|
@@ -981,11 +1021,11 @@ ENCRYPTION_KEY=${s()}
|
|
|
981
1021
|
name: "shouldStart",
|
|
982
1022
|
message: "Would you like to start the development server?",
|
|
983
1023
|
default: !0
|
|
984
|
-
}])).shouldStart, n && (m === "cloudflare" ? C() : m === "aws-sst" ? v() : m === "proxy" ?
|
|
1024
|
+
}])).shouldStart, n && (m === "cloudflare" ? C() : m === "cloudflare-wfp-dispatcher" ? w() : m === "aws-sst" ? v() : m === "proxy" ? T() : E(), 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 === "aws-sst" ? v() : m === "proxy" ? T() : E());
|
|
985
1025
|
} catch (e) {
|
|
986
1026
|
console.error("\n❌ An error occurred:", e), process.exit(1);
|
|
987
1027
|
}
|
|
988
1028
|
}
|
|
989
|
-
|
|
1029
|
+
L || (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 === "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 : 3e3}`), D && (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: ${O}`)), console.log("\nFor more information, visit: https://authhero.net/docs\n"));
|
|
990
1030
|
}), c.parse(process.argv);
|
|
991
1031
|
//#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
|
};
|