create-authhero 0.5.0 → 0.7.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-multitenant/.dev.vars.example +9 -0
- package/dist/cloudflare-multitenant/README.md +177 -0
- package/dist/cloudflare-multitenant/src/app.ts +47 -0
- package/dist/cloudflare-multitenant/src/database-factory.ts +220 -0
- package/dist/cloudflare-multitenant/src/index.ts +71 -0
- package/dist/cloudflare-multitenant/src/types.ts +24 -0
- package/dist/cloudflare-multitenant/tsconfig.json +14 -0
- package/dist/cloudflare-multitenant/wrangler.toml +38 -0
- package/dist/cloudflare-simple/README.md +75 -0
- package/dist/cloudflare-simple/src/app.ts +26 -0
- package/dist/cloudflare-simple/src/index.ts +27 -0
- package/dist/cloudflare-simple/src/types.ts +5 -0
- package/dist/cloudflare-simple/tsconfig.json +14 -0
- package/dist/cloudflare-simple/wrangler.toml +21 -0
- package/dist/create-authhero.js +344 -0
- package/dist/local/README.md +50 -0
- package/dist/local/src/app.ts +26 -0
- package/dist/local/src/index.ts +117 -0
- package/dist/local/src/migrate.ts +25 -0
- package/dist/local/tsconfig.json +16 -0
- package/index.js +3 -0
- package/package.json +4 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Context } from "hono";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
import { AuthHeroConfig, init } from "authhero";
|
|
4
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
5
|
+
|
|
6
|
+
export default function createApp(config: AuthHeroConfig) {
|
|
7
|
+
const { app } = init(config);
|
|
8
|
+
|
|
9
|
+
app
|
|
10
|
+
.onError((err, ctx) => {
|
|
11
|
+
if (err instanceof HTTPException) {
|
|
12
|
+
return err.getResponse();
|
|
13
|
+
}
|
|
14
|
+
console.error(err);
|
|
15
|
+
return ctx.text(err.message, 500);
|
|
16
|
+
})
|
|
17
|
+
.get("/", async (ctx: Context) => {
|
|
18
|
+
return ctx.json({
|
|
19
|
+
name: "AuthHero Server",
|
|
20
|
+
status: "running",
|
|
21
|
+
});
|
|
22
|
+
})
|
|
23
|
+
.get("/docs", swaggerUI({ url: "/api/v2/spec" }));
|
|
24
|
+
|
|
25
|
+
return app;
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
+
import { D1Dialect } from "kysely-d1";
|
|
3
|
+
import { Kysely } from "kysely";
|
|
4
|
+
import createAdapters from "@authhero/kysely-adapter";
|
|
5
|
+
import createApp from "./app";
|
|
6
|
+
import { Env } from "./types";
|
|
7
|
+
import { AuthHeroConfig, Bindings, Variables } from "authhero";
|
|
8
|
+
|
|
9
|
+
let app: OpenAPIHono<{ Bindings: Bindings; Variables: Variables }> | undefined;
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
13
|
+
if (!app) {
|
|
14
|
+
const dialect = new D1Dialect({ database: env.AUTH_DB });
|
|
15
|
+
const db = new Kysely<any>({ dialect });
|
|
16
|
+
const dataAdapter = createAdapters(db);
|
|
17
|
+
|
|
18
|
+
const config: AuthHeroConfig = {
|
|
19
|
+
dataAdapter,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
app = createApp(config);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return app.fetch(request, env);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -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,21 @@
|
|
|
1
|
+
name = "authhero-server"
|
|
2
|
+
main = "src/index.ts"
|
|
3
|
+
compatibility_date = "2024-11-20"
|
|
4
|
+
|
|
5
|
+
# D1 Database binding
|
|
6
|
+
# Run: wrangler d1 create authhero-db
|
|
7
|
+
# Then uncomment and update the database_id below:
|
|
8
|
+
# [[d1_databases]]
|
|
9
|
+
# binding = "AUTH_DB"
|
|
10
|
+
# database_name = "authhero-db"
|
|
11
|
+
# database_id = "<YOUR_DATABASE_ID>"
|
|
12
|
+
|
|
13
|
+
# For local development, you can use a local D1 database:
|
|
14
|
+
[[d1_databases]]
|
|
15
|
+
binding = "AUTH_DB"
|
|
16
|
+
database_name = "authhero-db"
|
|
17
|
+
database_id = "local"
|
|
18
|
+
|
|
19
|
+
# Optional: Enable observability
|
|
20
|
+
# [observability]
|
|
21
|
+
# enabled = true
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command as b } from "commander";
|
|
3
|
+
import d from "inquirer";
|
|
4
|
+
import l from "fs";
|
|
5
|
+
import c from "path";
|
|
6
|
+
import { spawn as f } from "child_process";
|
|
7
|
+
const w = new b(), r = {
|
|
8
|
+
local: {
|
|
9
|
+
name: "Local (SQLite)",
|
|
10
|
+
description: "Local development setup with SQLite database - great for getting started",
|
|
11
|
+
templateDir: "local",
|
|
12
|
+
packageJson: (t) => ({
|
|
13
|
+
name: t,
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
type: "module",
|
|
16
|
+
scripts: {
|
|
17
|
+
dev: "npx tsx watch src/index.ts",
|
|
18
|
+
start: "npx tsx src/index.ts",
|
|
19
|
+
migrate: "npx tsx src/migrate.ts",
|
|
20
|
+
seed: "npx tsx src/seed.ts"
|
|
21
|
+
},
|
|
22
|
+
dependencies: {
|
|
23
|
+
"@authhero/kysely-adapter": "latest",
|
|
24
|
+
"@hono/swagger-ui": "^0.5.0",
|
|
25
|
+
"@hono/zod-openapi": "^0.19.0",
|
|
26
|
+
"@hono/node-server": "latest",
|
|
27
|
+
authhero: "latest",
|
|
28
|
+
"better-sqlite3": "latest",
|
|
29
|
+
hono: "^4.6.0",
|
|
30
|
+
kysely: "latest"
|
|
31
|
+
},
|
|
32
|
+
devDependencies: {
|
|
33
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
tsx: "^4.0.0",
|
|
36
|
+
typescript: "^5.5.0"
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
seedFile: "seed.ts"
|
|
40
|
+
},
|
|
41
|
+
"cloudflare-simple": {
|
|
42
|
+
name: "Cloudflare Simple (Single Tenant)",
|
|
43
|
+
description: "Single-tenant Cloudflare Workers setup with D1 database",
|
|
44
|
+
templateDir: "cloudflare-simple",
|
|
45
|
+
packageJson: (t) => ({
|
|
46
|
+
name: t,
|
|
47
|
+
version: "1.0.0",
|
|
48
|
+
type: "module",
|
|
49
|
+
scripts: {
|
|
50
|
+
dev: "wrangler dev",
|
|
51
|
+
deploy: "wrangler deploy",
|
|
52
|
+
"db:migrate": "wrangler d1 migrations apply AUTH_DB --local",
|
|
53
|
+
"db:migrate:prod": "wrangler d1 migrations apply AUTH_DB --remote",
|
|
54
|
+
seed: "wrangler d1 execute AUTH_DB --local --file=seed.sql"
|
|
55
|
+
},
|
|
56
|
+
dependencies: {
|
|
57
|
+
"@authhero/kysely-adapter": "latest",
|
|
58
|
+
"@hono/swagger-ui": "^0.5.0",
|
|
59
|
+
"@hono/zod-openapi": "^0.19.0",
|
|
60
|
+
authhero: "latest",
|
|
61
|
+
hono: "^4.6.0",
|
|
62
|
+
kysely: "latest",
|
|
63
|
+
"kysely-d1": "latest"
|
|
64
|
+
},
|
|
65
|
+
devDependencies: {
|
|
66
|
+
"@cloudflare/workers-types": "^4.0.0",
|
|
67
|
+
typescript: "^5.5.0",
|
|
68
|
+
wrangler: "^3.0.0"
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
seedFile: "seed.sql"
|
|
72
|
+
},
|
|
73
|
+
"cloudflare-multitenant": {
|
|
74
|
+
name: "Cloudflare Multi-Tenant (Production)",
|
|
75
|
+
description: "Production-grade multi-tenant setup with per-tenant D1 databases and Analytics Engine",
|
|
76
|
+
templateDir: "cloudflare-multitenant",
|
|
77
|
+
packageJson: (t) => ({
|
|
78
|
+
name: t,
|
|
79
|
+
version: "1.0.0",
|
|
80
|
+
type: "module",
|
|
81
|
+
scripts: {
|
|
82
|
+
dev: "wrangler dev",
|
|
83
|
+
deploy: "wrangler deploy",
|
|
84
|
+
"db:migrate": "wrangler d1 migrations apply MAIN_DB --local",
|
|
85
|
+
"db:migrate:prod": "wrangler d1 migrations apply MAIN_DB --remote",
|
|
86
|
+
seed: "wrangler d1 execute MAIN_DB --local --file=seed.sql"
|
|
87
|
+
},
|
|
88
|
+
dependencies: {
|
|
89
|
+
"@authhero/cloudflare-adapter": "latest",
|
|
90
|
+
"@authhero/kysely-adapter": "latest",
|
|
91
|
+
"@authhero/multi-tenancy": "latest",
|
|
92
|
+
"@hono/swagger-ui": "^0.5.0",
|
|
93
|
+
"@hono/zod-openapi": "^0.19.10",
|
|
94
|
+
authhero: "latest",
|
|
95
|
+
hono: "^4.6.0",
|
|
96
|
+
kysely: "latest",
|
|
97
|
+
"kysely-d1": "latest",
|
|
98
|
+
wretch: "^3.0.0"
|
|
99
|
+
},
|
|
100
|
+
devDependencies: {
|
|
101
|
+
"@cloudflare/workers-types": "^4.0.0",
|
|
102
|
+
typescript: "^5.5.0",
|
|
103
|
+
wrangler: "^3.0.0"
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
seedFile: "seed.sql"
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function v(t, e) {
|
|
110
|
+
l.readdirSync(t).forEach((s) => {
|
|
111
|
+
const n = c.join(t, s), o = c.join(e, s);
|
|
112
|
+
l.lstatSync(n).isDirectory() ? (l.mkdirSync(o, { recursive: !0 }), v(n, o)) : l.copyFileSync(n, o);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function D(t) {
|
|
116
|
+
if (t === "local")
|
|
117
|
+
return `import { SqliteDialect, Kysely } from "kysely";
|
|
118
|
+
import Database from "better-sqlite3";
|
|
119
|
+
import createAdapters from "@authhero/kysely-adapter";
|
|
120
|
+
import { seed } from "authhero";
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const adminEmail = process.argv[2] || process.env.ADMIN_EMAIL;
|
|
124
|
+
const adminPassword = process.argv[3] || process.env.ADMIN_PASSWORD;
|
|
125
|
+
|
|
126
|
+
if (!adminEmail || !adminPassword) {
|
|
127
|
+
console.error("Usage: npm run seed <email> <password>");
|
|
128
|
+
console.error(" or: ADMIN_EMAIL=... ADMIN_PASSWORD=... npm run seed");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const dialect = new SqliteDialect({
|
|
133
|
+
database: new Database("db.sqlite"),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const db = new Kysely<any>({ dialect });
|
|
137
|
+
const adapters = createAdapters(db);
|
|
138
|
+
|
|
139
|
+
await seed(adapters, {
|
|
140
|
+
adminEmail,
|
|
141
|
+
adminPassword,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await db.destroy();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch(console.error);
|
|
148
|
+
`;
|
|
149
|
+
{
|
|
150
|
+
const e = (/* @__PURE__ */ new Date()).toISOString(), a = "default";
|
|
151
|
+
return `-- Seed file for AuthHero
|
|
152
|
+
--
|
|
153
|
+
-- IMPORTANT: This SQL file creates the basic structure but the password
|
|
154
|
+
-- cannot be properly hashed in SQL. After running this seed, you should
|
|
155
|
+
-- use the management API or run a script to set the admin password.
|
|
156
|
+
|
|
157
|
+
-- Create default tenant
|
|
158
|
+
INSERT OR IGNORE INTO tenants (id, friendly_name, audience, sender_email, sender_name, created_at, updated_at)
|
|
159
|
+
VALUES ('${a}', 'Default Tenant', 'https://api.example.com', 'noreply@example.com', 'AuthHero', '${e}', '${e}');
|
|
160
|
+
|
|
161
|
+
-- Create password connection
|
|
162
|
+
INSERT OR IGNORE INTO connections (id, tenant_id, name, strategy, options, created_at, updated_at)
|
|
163
|
+
VALUES ('conn_default', '${a}', 'Username-Password-Authentication', 'Username-Password-Authentication', '{}', '${e}', '${e}');
|
|
164
|
+
|
|
165
|
+
-- Create default client
|
|
166
|
+
INSERT OR IGNORE INTO clients (client_id, tenant_id, name, callbacks, allowed_origins, web_origins, connections, created_at, updated_at)
|
|
167
|
+
VALUES ('default', '${a}', 'Default Application', '["https://manage.authhero.net/auth-callback","https://local.authhero.net/auth-callback"]', '[]', '[]', '["Username-Password-Authentication"]', '${e}', '${e}');
|
|
168
|
+
|
|
169
|
+
-- Note: Admin user and password should be created via the management API
|
|
170
|
+
-- or using a TypeScript seed script with proper bcrypt hashing.
|
|
171
|
+
-- Example command: curl -X POST http://localhost:3000/api/v2/users ...
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function u(t, e) {
|
|
176
|
+
return new Promise((a, s) => {
|
|
177
|
+
const n = f(t, [], {
|
|
178
|
+
cwd: e,
|
|
179
|
+
shell: !0,
|
|
180
|
+
stdio: "inherit"
|
|
181
|
+
});
|
|
182
|
+
n.on("close", (o) => {
|
|
183
|
+
o === 0 ? a() : s(new Error(`Command failed with exit code ${o}`));
|
|
184
|
+
}), n.on("error", s);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function k(t, e, a) {
|
|
188
|
+
return new Promise((s, n) => {
|
|
189
|
+
const o = f(t, [], {
|
|
190
|
+
cwd: e,
|
|
191
|
+
shell: !0,
|
|
192
|
+
stdio: "inherit",
|
|
193
|
+
env: { ...process.env, ...a }
|
|
194
|
+
});
|
|
195
|
+
o.on("close", (m) => {
|
|
196
|
+
m === 0 ? s() : n(new Error(`Command failed with exit code ${m}`));
|
|
197
|
+
}), o.on("error", n);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
w.version("1.0.0").description("Create a new AuthHero project").argument("[project-name]", "name of the project").action(async (t) => {
|
|
201
|
+
console.log(`
|
|
202
|
+
🔐 Welcome to AuthHero!
|
|
203
|
+
`), t || (t = (await d.prompt([
|
|
204
|
+
{
|
|
205
|
+
type: "input",
|
|
206
|
+
name: "projectName",
|
|
207
|
+
message: "Project name:",
|
|
208
|
+
default: "auth-server",
|
|
209
|
+
validate: (p) => p !== "" || "Project name cannot be empty"
|
|
210
|
+
}
|
|
211
|
+
])).projectName);
|
|
212
|
+
const e = c.join(process.cwd(), t);
|
|
213
|
+
l.existsSync(e) && (console.error(`❌ Project "${t}" already exists.`), process.exit(1));
|
|
214
|
+
const { setupType: a } = await d.prompt([
|
|
215
|
+
{
|
|
216
|
+
type: "list",
|
|
217
|
+
name: "setupType",
|
|
218
|
+
message: "Select your setup type:",
|
|
219
|
+
choices: [
|
|
220
|
+
{
|
|
221
|
+
name: `${r.local.name}
|
|
222
|
+
${r.local.description}`,
|
|
223
|
+
value: "local",
|
|
224
|
+
short: r.local.name
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: `${r["cloudflare-simple"].name}
|
|
228
|
+
${r["cloudflare-simple"].description}`,
|
|
229
|
+
value: "cloudflare-simple",
|
|
230
|
+
short: r["cloudflare-simple"].name
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: `${r["cloudflare-multitenant"].name}
|
|
234
|
+
${r["cloudflare-multitenant"].description}`,
|
|
235
|
+
value: "cloudflare-multitenant",
|
|
236
|
+
short: r["cloudflare-multitenant"].name
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
]), s = r[a];
|
|
241
|
+
l.mkdirSync(e, { recursive: !0 }), l.writeFileSync(
|
|
242
|
+
c.join(e, "package.json"),
|
|
243
|
+
JSON.stringify(s.packageJson(t), null, 2)
|
|
244
|
+
);
|
|
245
|
+
const n = c.join(
|
|
246
|
+
import.meta.url.replace("file://", "").replace("/create-authhero.js", ""),
|
|
247
|
+
s.templateDir
|
|
248
|
+
);
|
|
249
|
+
l.existsSync(n) ? v(n, e) : (console.error(`❌ Template directory not found: ${n}`), process.exit(1));
|
|
250
|
+
const o = D(a), m = a === "local" ? "src/seed.ts" : "seed.sql";
|
|
251
|
+
l.writeFileSync(c.join(e, m), o), console.log(
|
|
252
|
+
`
|
|
253
|
+
✅ Project "${t}" has been created with ${s.name} setup!
|
|
254
|
+
`
|
|
255
|
+
);
|
|
256
|
+
const { shouldInstall: h } = await d.prompt([
|
|
257
|
+
{
|
|
258
|
+
type: "confirm",
|
|
259
|
+
name: "shouldInstall",
|
|
260
|
+
message: "Would you like to install dependencies now?",
|
|
261
|
+
default: !0
|
|
262
|
+
}
|
|
263
|
+
]);
|
|
264
|
+
if (h) {
|
|
265
|
+
const { packageManager: i } = await d.prompt([
|
|
266
|
+
{
|
|
267
|
+
type: "list",
|
|
268
|
+
name: "packageManager",
|
|
269
|
+
message: "Which package manager would you like to use?",
|
|
270
|
+
choices: [
|
|
271
|
+
{ name: "npm", value: "npm" },
|
|
272
|
+
{ name: "yarn", value: "yarn" },
|
|
273
|
+
{ name: "pnpm", value: "pnpm" },
|
|
274
|
+
{ name: "bun", value: "bun" }
|
|
275
|
+
],
|
|
276
|
+
default: "npm"
|
|
277
|
+
}
|
|
278
|
+
]);
|
|
279
|
+
console.log(`
|
|
280
|
+
📦 Installing dependencies with ${i}...
|
|
281
|
+
`);
|
|
282
|
+
try {
|
|
283
|
+
const p = i === "pnpm" ? "pnpm install --ignore-workspace" : `${i} install`;
|
|
284
|
+
if (await u(p, e), a === "local" && (console.log(`
|
|
285
|
+
🔧 Building native modules...
|
|
286
|
+
`), await u("npm rebuild better-sqlite3", e)), console.log(`
|
|
287
|
+
✅ Dependencies installed successfully!
|
|
288
|
+
`), a === "local") {
|
|
289
|
+
const { shouldSetup: A } = await d.prompt([
|
|
290
|
+
{
|
|
291
|
+
type: "confirm",
|
|
292
|
+
name: "shouldSetup",
|
|
293
|
+
message: "Would you like to run migrations and seed the database?",
|
|
294
|
+
default: !0
|
|
295
|
+
}
|
|
296
|
+
]);
|
|
297
|
+
if (A) {
|
|
298
|
+
const y = await d.prompt([
|
|
299
|
+
{
|
|
300
|
+
type: "input",
|
|
301
|
+
name: "username",
|
|
302
|
+
message: "Admin email:",
|
|
303
|
+
default: "admin@example.com",
|
|
304
|
+
validate: (g) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(g) || "Please enter a valid email address"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
type: "password",
|
|
308
|
+
name: "password",
|
|
309
|
+
message: "Admin password:",
|
|
310
|
+
mask: "*",
|
|
311
|
+
validate: (g) => g.length < 8 ? "Password must be at least 8 characters" : !0
|
|
312
|
+
}
|
|
313
|
+
]);
|
|
314
|
+
console.log(`
|
|
315
|
+
🔄 Running migrations...
|
|
316
|
+
`), await u(`${i} run migrate`, e), console.log(`
|
|
317
|
+
🌱 Seeding database...
|
|
318
|
+
`), await k(`${i} run seed`, e, {
|
|
319
|
+
ADMIN_EMAIL: y.username,
|
|
320
|
+
ADMIN_PASSWORD: y.password
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const { shouldStart: S } = await d.prompt([
|
|
325
|
+
{
|
|
326
|
+
type: "confirm",
|
|
327
|
+
name: "shouldStart",
|
|
328
|
+
message: "Would you like to start the development server?",
|
|
329
|
+
default: !0
|
|
330
|
+
}
|
|
331
|
+
]);
|
|
332
|
+
S && (console.log(`
|
|
333
|
+
🚀 Starting development server...
|
|
334
|
+
`), await u(`${i} run dev`, e));
|
|
335
|
+
} catch (p) {
|
|
336
|
+
console.error(`
|
|
337
|
+
❌ An error occurred:`, p);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
h || (console.log("Next steps:"), console.log(` cd ${t}`), a === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(" npm run seed -- <email> <password>"), console.log(" npm run dev")) : (console.log(" npm install"), console.log(" npm run db:migrate"), console.log(" npm run seed"), console.log(" npm run dev")), console.log(`
|
|
341
|
+
For more information, visit: https://authhero.net/docs
|
|
342
|
+
`));
|
|
343
|
+
});
|
|
344
|
+
w.parse(process.argv);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# AuthHero Local Server
|
|
2
|
+
|
|
3
|
+
A local AuthHero authentication server using SQLite for development.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Run database migrations:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run migrate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. Start the development server:
|
|
20
|
+
```bash
|
|
21
|
+
npm run dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The server will be available at `http://localhost:3000`.
|
|
25
|
+
|
|
26
|
+
## Project Structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
├── src/
|
|
30
|
+
│ ├── index.ts # Server entry point
|
|
31
|
+
│ ├── app.ts # AuthHero app configuration
|
|
32
|
+
│ ├── migrate.ts # Database migration script
|
|
33
|
+
│ └── seed.ts # Database seeding script
|
|
34
|
+
├── db.sqlite # SQLite database (created after first run)
|
|
35
|
+
└── package.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API Documentation
|
|
39
|
+
|
|
40
|
+
Visit `http://localhost:3000/docs` to see the Swagger UI documentation.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
You can customize the AuthHero configuration in `src/app.ts`. Common options include:
|
|
45
|
+
|
|
46
|
+
- Custom hooks for login/signup events
|
|
47
|
+
- Custom email templates
|
|
48
|
+
- Session configuration
|
|
49
|
+
|
|
50
|
+
For more information, visit [https://authhero.net/docs](https://authhero.net/docs).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Context } from "hono";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
import { AuthHeroConfig, init } from "authhero";
|
|
4
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
5
|
+
|
|
6
|
+
export default function createApp(config: AuthHeroConfig) {
|
|
7
|
+
const { app } = init(config);
|
|
8
|
+
|
|
9
|
+
app
|
|
10
|
+
.onError((err, ctx) => {
|
|
11
|
+
if (err instanceof HTTPException) {
|
|
12
|
+
return err.getResponse();
|
|
13
|
+
}
|
|
14
|
+
console.error(err);
|
|
15
|
+
return ctx.text(err.message, 500);
|
|
16
|
+
})
|
|
17
|
+
.get("/", async (ctx: Context) => {
|
|
18
|
+
return ctx.json({
|
|
19
|
+
name: "AuthHero Server",
|
|
20
|
+
status: "running",
|
|
21
|
+
});
|
|
22
|
+
})
|
|
23
|
+
.get("/docs", swaggerUI({ url: "/api/v2/spec" }));
|
|
24
|
+
|
|
25
|
+
return app;
|
|
26
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { SqliteDialect } from "kysely";
|
|
3
|
+
import { Kysely } from "kysely";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import createAdapters from "@authhero/kysely-adapter";
|
|
6
|
+
import createApp from "./app";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import https from "https";
|
|
11
|
+
|
|
12
|
+
// Generate self-signed certificates for local HTTPS if they don't exist
|
|
13
|
+
const certDir = path.join(process.cwd(), ".certs");
|
|
14
|
+
const keyPath = path.join(certDir, "localhost-key.pem");
|
|
15
|
+
const certPath = path.join(certDir, "localhost.pem");
|
|
16
|
+
|
|
17
|
+
function ensureCertificates() {
|
|
18
|
+
if (!fs.existsSync(certDir)) {
|
|
19
|
+
fs.mkdirSync(certDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) {
|
|
23
|
+
console.log("🔑 Generating self-signed certificates for local HTTPS...");
|
|
24
|
+
|
|
25
|
+
// Try mkcert first (if installed), otherwise fall back to openssl
|
|
26
|
+
try {
|
|
27
|
+
execSync(`which mkcert`, { stdio: "ignore" });
|
|
28
|
+
execSync(
|
|
29
|
+
`mkcert -key-file ${keyPath} -cert-file ${certPath} localhost 127.0.0.1`,
|
|
30
|
+
{
|
|
31
|
+
stdio: "inherit",
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
console.log("✅ Certificates generated with mkcert");
|
|
35
|
+
} catch {
|
|
36
|
+
// Fall back to openssl
|
|
37
|
+
try {
|
|
38
|
+
execSync(
|
|
39
|
+
`openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 365 -nodes -subj "/CN=localhost"`,
|
|
40
|
+
{ stdio: "inherit" },
|
|
41
|
+
);
|
|
42
|
+
console.log("✅ Self-signed certificates generated with openssl");
|
|
43
|
+
console.log(
|
|
44
|
+
"⚠️ You may need to trust the certificate in your browser",
|
|
45
|
+
);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(
|
|
48
|
+
"❌ Failed to generate certificates. Please install mkcert or openssl",
|
|
49
|
+
);
|
|
50
|
+
console.error(
|
|
51
|
+
" Install mkcert: brew install mkcert && mkcert -install",
|
|
52
|
+
);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
key: fs.readFileSync(keyPath),
|
|
60
|
+
cert: fs.readFileSync(certPath),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Initialize SQLite database
|
|
65
|
+
let db: Kysely<any>;
|
|
66
|
+
try {
|
|
67
|
+
const dialect = new SqliteDialect({
|
|
68
|
+
database: new Database("db.sqlite"),
|
|
69
|
+
});
|
|
70
|
+
db = new Kysely<any>({ dialect });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("❌ Failed to initialize database:");
|
|
73
|
+
console.error(
|
|
74
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
75
|
+
);
|
|
76
|
+
console.error("\nPossible causes:");
|
|
77
|
+
console.error(" - File permissions issue");
|
|
78
|
+
console.error(" - Disk space is full");
|
|
79
|
+
console.error(" - Database file is corrupted");
|
|
80
|
+
console.error("\nTry running: npm run migrate");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const dataAdapter = createAdapters(db);
|
|
85
|
+
|
|
86
|
+
// Create the AuthHero app
|
|
87
|
+
const app = createApp({
|
|
88
|
+
dataAdapter,
|
|
89
|
+
allowedOrigins: [
|
|
90
|
+
"https://manage.authhero.net",
|
|
91
|
+
"https://local.authhero.net",
|
|
92
|
+
"http://localhost:5173",
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Start the server
|
|
97
|
+
const port = Number(process.env.PORT) || 3000;
|
|
98
|
+
const issuer = process.env.ISSUER || `https://localhost:${port}/`;
|
|
99
|
+
|
|
100
|
+
// Get or generate certificates
|
|
101
|
+
const { key, cert } = ensureCertificates();
|
|
102
|
+
|
|
103
|
+
console.log(`🔐 AuthHero server running at https://localhost:${port}`);
|
|
104
|
+
console.log(`📚 API documentation available at https://localhost:${port}/docs`);
|
|
105
|
+
console.log(`🌐 Portal available at https://local.authhero.net`);
|
|
106
|
+
|
|
107
|
+
serve({
|
|
108
|
+
fetch: (request) => {
|
|
109
|
+
return app.fetch(request, {
|
|
110
|
+
ISSUER: issuer,
|
|
111
|
+
data: dataAdapter,
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
port,
|
|
115
|
+
createServer: https.createServer,
|
|
116
|
+
serverOptions: { key, cert },
|
|
117
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SqliteDialect, Kysely } from "kysely";
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import { migrateToLatest } from "@authhero/kysely-adapter";
|
|
4
|
+
|
|
5
|
+
async function migrate() {
|
|
6
|
+
const dialect = new SqliteDialect({
|
|
7
|
+
database: new Database("db.sqlite"),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const db = new Kysely<any>({ dialect });
|
|
11
|
+
|
|
12
|
+
console.log("Running migrations...");
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await migrateToLatest(db, true);
|
|
16
|
+
console.log("✅ All migrations completed successfully");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error("Migration failed:", error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
} finally {
|
|
21
|
+
await db.destroy();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
migrate();
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules"]
|
|
16
|
+
}
|
package/index.js
ADDED