jsxserve 0.1.2 → 0.1.4

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 (39) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/build.js +13 -3
  3. package/dist/cli/commands/build.js.map +1 -1
  4. package/dist/cli/commands/init.d.ts.map +1 -1
  5. package/dist/cli/commands/init.js +54 -3
  6. package/dist/cli/commands/init.js.map +1 -1
  7. package/dist/cli/templates/auth.d.ts +17 -0
  8. package/dist/cli/templates/auth.d.ts.map +1 -0
  9. package/dist/cli/templates/auth.js +516 -0
  10. package/dist/cli/templates/auth.js.map +1 -0
  11. package/dist/cli/templates/project.d.ts +9 -2
  12. package/dist/cli/templates/project.d.ts.map +1 -1
  13. package/dist/cli/templates/project.js +112 -11
  14. package/dist/cli/templates/project.js.map +1 -1
  15. package/dist/components/Controller.d.ts +1 -0
  16. package/dist/components/Controller.d.ts.map +1 -1
  17. package/dist/components/Controller.js +4 -0
  18. package/dist/components/Controller.js.map +1 -1
  19. package/dist/config/config-controller.d.ts +5 -0
  20. package/dist/config/config-controller.d.ts.map +1 -0
  21. package/dist/config/config-controller.js +8 -0
  22. package/dist/config/config-controller.js.map +1 -0
  23. package/dist/config/config.d.ts +18 -0
  24. package/dist/config/config.d.ts.map +1 -0
  25. package/dist/config/config.js +106 -0
  26. package/dist/config/config.js.map +1 -0
  27. package/dist/config/index.d.ts +4 -0
  28. package/dist/config/index.d.ts.map +1 -0
  29. package/dist/config/index.js +3 -0
  30. package/dist/config/index.js.map +1 -0
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/validation.d.ts +1 -1
  36. package/dist/validation.d.ts.map +1 -1
  37. package/dist/validation.js +1 -0
  38. package/dist/validation.js.map +1 -1
  39. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/build.ts"],"names":[],"mappings":"AAEA,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3C"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/build.ts"],"names":[],"mappings":"AAEA,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB3C"}
@@ -1,11 +1,21 @@
1
1
  import { spawn } from "node:child_process";
2
2
  export async function build() {
3
- const child = spawn("npx", ["tsc"], {
3
+ const tsc = spawn("npx", ["tsc"], {
4
4
  stdio: "inherit",
5
5
  shell: false,
6
6
  });
7
- child.on("close", (code) => {
8
- process.exit(code ?? 0);
7
+ tsc.on("close", (code) => {
8
+ if (code !== 0) {
9
+ process.exit(code ?? 1);
10
+ }
11
+ // Rewrite path aliases in compiled output
12
+ const alias = spawn("npx", ["tsc-alias"], {
13
+ stdio: "inherit",
14
+ shell: false,
15
+ });
16
+ alias.on("close", (aliasCode) => {
17
+ process.exit(aliasCode ?? 0);
18
+ });
9
19
  });
10
20
  }
11
21
  //# sourceMappingURL=build.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/cli/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;QAClC,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/cli/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE;QAChC,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QACvB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;QAC1B,CAAC;QAED,0CAA0C;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE;YACxC,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE;YAC9B,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAuBA,wBAAsB,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD9D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AA0CA,wBAAsB,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuI9D"}
@@ -2,7 +2,8 @@ import { join, basename } from "node:path";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import { writeFileWithLog, mkdirIfNeeded } from "../utils/fs.js";
4
4
  import { ask, select, confirm } from "../utils/prompt.js";
5
- import { appTemplate, homeControllerTemplate, tsconfigTemplate, packageJsonTemplate, gitignoreTemplate, } from "../templates/project.js";
5
+ import { appTemplate, homeControllerTemplate, tsconfigTemplate, packageJsonTemplate, envTemplate, gitignoreTemplate, } from "../templates/project.js";
6
+ import { authJwtTemplate, authPasswordTemplate, authOAuthUtilTemplate, userModelTemplate, oauthAccountModelTemplate, refreshTokenModelTemplate, userMigrationTemplate, oauthAccountMigrationTemplate, refreshTokenMigrationTemplate, authMiddlewareTemplate, authRegisterControllerTemplate, authLoginControllerTemplate, authRefreshControllerTemplate, authLogoutControllerTemplate, authOAuthControllerTemplate, authOAuthCallbackControllerTemplate, } from "../templates/auth.js";
6
7
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
7
8
  async function isDirEmpty(path) {
8
9
  try {
@@ -22,6 +23,18 @@ export async function init(projectName) {
22
23
  "mongodb",
23
24
  "none",
24
25
  ]);
26
+ let authEnabled = false;
27
+ let googleOAuth = false;
28
+ let facebookOAuth = false;
29
+ let githubOAuth = false;
30
+ if (dialect !== "none") {
31
+ authEnabled = await confirm("Enable authentication?");
32
+ if (authEnabled) {
33
+ googleOAuth = await confirm("Enable Google login?");
34
+ facebookOAuth = await confirm("Enable Facebook login?");
35
+ githubOAuth = await confirm("Enable GitHub login?");
36
+ }
37
+ }
25
38
  const targetDir = join(process.cwd(), name);
26
39
  if (!(await isDirEmpty(targetDir))) {
27
40
  const proceed = await confirm("Directory is not empty. Continue?");
@@ -38,14 +51,52 @@ export async function init(projectName) {
38
51
  await mkdirIfNeeded(join(targetDir, "migrations"));
39
52
  }
40
53
  }
54
+ if (authEnabled) {
55
+ await mkdirIfNeeded(join(targetDir, "src", "auth"));
56
+ await mkdirIfNeeded(join(targetDir, "src", "controllers", "auth"));
57
+ await mkdirIfNeeded(join(targetDir, "src", "middleware"));
58
+ if (googleOAuth || facebookOAuth || githubOAuth) {
59
+ await mkdirIfNeeded(join(targetDir, "src", "auth", "oauth"));
60
+ }
61
+ }
62
+ // Build auth config for templates
63
+ const authConfig = authEnabled
64
+ ? { enabled: true, google: googleOAuth, facebook: facebookOAuth, github: githubOAuth }
65
+ : undefined;
41
66
  // Write files
42
67
  const files = [
43
- [join(targetDir, "package.json"), packageJsonTemplate(name, dialect)],
68
+ [join(targetDir, "package.json"), packageJsonTemplate(name, dialect, authEnabled)],
44
69
  [join(targetDir, "tsconfig.json"), tsconfigTemplate()],
70
+ [join(targetDir, ".env"), envTemplate(dialect, authConfig)],
45
71
  [join(targetDir, ".gitignore"), gitignoreTemplate()],
46
- [join(targetDir, "src", "app.tsx"), appTemplate(dialect)],
72
+ [join(targetDir, "src", "app.tsx"), appTemplate(dialect, authConfig)],
47
73
  [join(targetDir, "src", "controllers", "home.ts"), homeControllerTemplate()],
48
74
  ];
75
+ if (authEnabled) {
76
+ // Core auth files
77
+ files.push([join(targetDir, "src", "auth", "jwt.ts"), authJwtTemplate()], [join(targetDir, "src", "auth", "password.ts"), authPasswordTemplate()]);
78
+ // Models
79
+ files.push([join(targetDir, "src", "models", "User.ts"), userModelTemplate()], [join(targetDir, "src", "models", "OAuthAccount.ts"), oauthAccountModelTemplate()], [join(targetDir, "src", "models", "RefreshToken.ts"), refreshTokenModelTemplate()]);
80
+ // Middleware
81
+ files.push([join(targetDir, "src", "middleware", "auth.ts"), authMiddlewareTemplate()]);
82
+ // Auth controllers
83
+ files.push([join(targetDir, "src", "controllers", "auth", "register.ts"), authRegisterControllerTemplate()], [join(targetDir, "src", "controllers", "auth", "login.ts"), authLoginControllerTemplate()], [join(targetDir, "src", "controllers", "auth", "refresh.ts"), authRefreshControllerTemplate()], [join(targetDir, "src", "controllers", "auth", "logout.ts"), authLogoutControllerTemplate()]);
84
+ // Migrations (SQL only)
85
+ if (dialect !== "mongodb") {
86
+ files.push([join(targetDir, "migrations", "001_create_users.ts"), userMigrationTemplate(dialect)], [join(targetDir, "migrations", "002_create_oauth_accounts.ts"), oauthAccountMigrationTemplate(dialect)], [join(targetDir, "migrations", "003_create_refresh_tokens.ts"), refreshTokenMigrationTemplate(dialect)]);
87
+ }
88
+ // OAuth per-provider files
89
+ const providers = [
90
+ { key: "google", enabled: googleOAuth },
91
+ { key: "facebook", enabled: facebookOAuth },
92
+ { key: "github", enabled: githubOAuth },
93
+ ];
94
+ for (const { key, enabled } of providers) {
95
+ if (enabled) {
96
+ files.push([join(targetDir, "src", "auth", "oauth", `${key}.ts`), authOAuthUtilTemplate(key)], [join(targetDir, "src", "controllers", "auth", `${key}.ts`), authOAuthControllerTemplate(key)], [join(targetDir, "src", "controllers", "auth", `${key}-callback.ts`), authOAuthCallbackControllerTemplate(key)]);
97
+ }
98
+ }
99
+ }
49
100
  for (const [path, content] of files) {
50
101
  await writeFileWithLog(path, content);
51
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export declare function authJwtTemplate(): string;
2
+ export declare function authPasswordTemplate(): string;
3
+ export declare function authOAuthUtilTemplate(provider: string): string;
4
+ export declare function userModelTemplate(): string;
5
+ export declare function oauthAccountModelTemplate(): string;
6
+ export declare function refreshTokenModelTemplate(): string;
7
+ export declare function userMigrationTemplate(dialect: string): string;
8
+ export declare function oauthAccountMigrationTemplate(dialect: string): string;
9
+ export declare function refreshTokenMigrationTemplate(dialect: string): string;
10
+ export declare function authMiddlewareTemplate(): string;
11
+ export declare function authRegisterControllerTemplate(): string;
12
+ export declare function authLoginControllerTemplate(): string;
13
+ export declare function authRefreshControllerTemplate(): string;
14
+ export declare function authLogoutControllerTemplate(): string;
15
+ export declare function authOAuthControllerTemplate(provider: string): string;
16
+ export declare function authOAuthCallbackControllerTemplate(provider: string): string;
17
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/cli/templates/auth.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,CA4BxC;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAqB7C;AAuCD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiD9D;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAe1C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAelD;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAclD;AAcD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwB7D;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBrE;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsBrE;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAuB/C;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CA8CvD;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CA0CpD;AAED,wBAAgB,6BAA6B,IAAI,MAAM,CA2CtD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAmBrD;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAepE;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoE5E"}
@@ -0,0 +1,516 @@
1
+ export function authJwtTemplate() {
2
+ return `import { SignJWT, jwtVerify } from "jose";
3
+
4
+ const secret = new TextEncoder().encode(process.env.JWT_SECRET);
5
+ const accessExpiry = process.env.JWT_ACCESS_EXPIRY || "15m";
6
+ const refreshExpiry = process.env.JWT_REFRESH_EXPIRY || "7d";
7
+
8
+ export async function signAccessToken(userId: string): Promise<string> {
9
+ return new SignJWT({ sub: userId })
10
+ .setProtectedHeader({ alg: "HS256" })
11
+ .setIssuedAt()
12
+ .setExpirationTime(accessExpiry)
13
+ .sign(secret);
14
+ }
15
+
16
+ export async function signRefreshToken(userId: string): Promise<string> {
17
+ return new SignJWT({ sub: userId })
18
+ .setProtectedHeader({ alg: "HS256" })
19
+ .setIssuedAt()
20
+ .setExpirationTime(refreshExpiry)
21
+ .sign(secret);
22
+ }
23
+
24
+ export async function verifyAccessToken(token: string): Promise<string> {
25
+ const { payload } = await jwtVerify(token, secret);
26
+ return payload.sub as string;
27
+ }
28
+ `;
29
+ }
30
+ export function authPasswordTemplate() {
31
+ return `import { scrypt, randomBytes, timingSafeEqual } from "node:crypto";
32
+ import { promisify } from "node:util";
33
+
34
+ const scryptAsync = promisify(scrypt);
35
+
36
+ export async function hashPassword(password: string): Promise<string> {
37
+ const salt = randomBytes(16).toString("hex");
38
+ const buf = (await scryptAsync(password, salt, 64)) as Buffer;
39
+ return salt + ":" + buf.toString("hex");
40
+ }
41
+
42
+ export async function verifyPassword(
43
+ stored: string,
44
+ supplied: string,
45
+ ): Promise<boolean> {
46
+ const [salt, hash] = stored.split(":");
47
+ const buf = (await scryptAsync(supplied, salt, 64)) as Buffer;
48
+ return timingSafeEqual(Buffer.from(hash, "hex"), buf);
49
+ }
50
+ `;
51
+ }
52
+ const oauthConfigs = {
53
+ google: {
54
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
55
+ tokenUrl: "https://oauth2.googleapis.com/token",
56
+ userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
57
+ scopes: "openid email profile",
58
+ envPrefix: "GOOGLE",
59
+ },
60
+ facebook: {
61
+ authUrl: "https://www.facebook.com/v18.0/dialog/oauth",
62
+ tokenUrl: "https://graph.facebook.com/v18.0/oauth/access_token",
63
+ userInfoUrl: "https://graph.facebook.com/me?fields=id,email,name",
64
+ scopes: "email",
65
+ envPrefix: "FACEBOOK",
66
+ },
67
+ github: {
68
+ authUrl: "https://github.com/login/oauth/authorize",
69
+ tokenUrl: "https://github.com/login/oauth/access_token",
70
+ userInfoUrl: "https://api.github.com/user",
71
+ scopes: "user:email",
72
+ envPrefix: "GITHUB",
73
+ },
74
+ };
75
+ function capitalize(s) {
76
+ return s.charAt(0).toUpperCase() + s.slice(1);
77
+ }
78
+ export function authOAuthUtilTemplate(provider) {
79
+ const config = oauthConfigs[provider];
80
+ const name = capitalize(provider);
81
+ return `const clientId = process.env.${config.envPrefix}_CLIENT_ID!;
82
+ const clientSecret = process.env.${config.envPrefix}_CLIENT_SECRET!;
83
+ const redirectUri = process.env.${config.envPrefix}_REDIRECT_URI!;
84
+
85
+ export function get${name}AuthUrl(): string {
86
+ const params = new URLSearchParams({
87
+ client_id: clientId,
88
+ redirect_uri: redirectUri,
89
+ response_type: "code",
90
+ scope: "${config.scopes}",
91
+ });
92
+ return "${config.authUrl}?" + params.toString();
93
+ }
94
+
95
+ export async function get${name}User(
96
+ code: string,
97
+ ): Promise<{ id: string; email: string; name: string }> {
98
+ const tokenRes = await fetch("${config.tokenUrl}", {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/x-www-form-urlencoded",
102
+ Accept: "application/json",
103
+ },
104
+ body: new URLSearchParams({
105
+ client_id: clientId,
106
+ client_secret: clientSecret,
107
+ code,
108
+ redirect_uri: redirectUri,
109
+ grant_type: "authorization_code",
110
+ }),
111
+ });
112
+ const tokens = await tokenRes.json();
113
+
114
+ const userRes = await fetch("${config.userInfoUrl}", {
115
+ headers: { Authorization: \`Bearer \${tokens.access_token}\` },
116
+ });
117
+ const user = await userRes.json();
118
+
119
+ return {
120
+ id: String(user.id ?? user.sub),
121
+ email: user.email,
122
+ name: user.name ?? user.login ?? "",
123
+ };
124
+ }
125
+ `;
126
+ }
127
+ export function userModelTemplate() {
128
+ return `import { Model, Field } from "jsxserve";
129
+
130
+ export class User extends Model {
131
+ static table = "users";
132
+
133
+ static schema = {
134
+ id: Field.serial().primaryKey(),
135
+ email: Field.text().notNull(),
136
+ password_hash: Field.text(),
137
+ name: Field.text().notNull(),
138
+ created_at: Field.text().notNull(),
139
+ };
140
+ }
141
+ `;
142
+ }
143
+ export function oauthAccountModelTemplate() {
144
+ return `import { Model, Field } from "jsxserve";
145
+
146
+ export class OAuthAccount extends Model {
147
+ static table = "oauth_accounts";
148
+
149
+ static schema = {
150
+ id: Field.serial().primaryKey(),
151
+ user_id: Field.integer().notNull(),
152
+ provider: Field.text().notNull(),
153
+ provider_user_id: Field.text().notNull(),
154
+ email: Field.text().notNull(),
155
+ };
156
+ }
157
+ `;
158
+ }
159
+ export function refreshTokenModelTemplate() {
160
+ return `import { Model, Field } from "jsxserve";
161
+
162
+ export class RefreshToken extends Model {
163
+ static table = "refresh_tokens";
164
+
165
+ static schema = {
166
+ id: Field.serial().primaryKey(),
167
+ user_id: Field.integer().notNull(),
168
+ token: Field.text().notNull(),
169
+ expires_at: Field.text().notNull(),
170
+ };
171
+ }
172
+ `;
173
+ }
174
+ function sqlTypes(dialect) {
175
+ const isPostgres = dialect === "postgres";
176
+ return {
177
+ serial: isPostgres ? "SERIAL" : "INTEGER",
178
+ autoIncrement: isPostgres ? "" : " AUTOINCREMENT",
179
+ primaryKey: isPostgres ? " PRIMARY KEY" : " PRIMARY KEY",
180
+ timestamp: isPostgres ? "TIMESTAMP DEFAULT NOW()" : "TEXT DEFAULT (datetime('now'))",
181
+ text: "TEXT",
182
+ integer: "INTEGER",
183
+ };
184
+ }
185
+ export function userMigrationTemplate(dialect) {
186
+ const t = sqlTypes(dialect);
187
+ const pk = dialect === "postgres"
188
+ ? `id ${t.serial} PRIMARY KEY`
189
+ : `id INTEGER PRIMARY KEY AUTOINCREMENT`;
190
+ return `import type { DatabaseAdapter } from "jsxserve";
191
+
192
+ export async function up(adapter: DatabaseAdapter): Promise<void> {
193
+ await adapter.raw(\`
194
+ CREATE TABLE IF NOT EXISTS users (
195
+ ${pk},
196
+ email TEXT NOT NULL UNIQUE,
197
+ password_hash TEXT,
198
+ name TEXT NOT NULL,
199
+ created_at ${t.timestamp}
200
+ )
201
+ \`);
202
+ }
203
+
204
+ export async function down(adapter: DatabaseAdapter): Promise<void> {
205
+ await adapter.raw("DROP TABLE IF EXISTS users");
206
+ }
207
+ `;
208
+ }
209
+ export function oauthAccountMigrationTemplate(dialect) {
210
+ const pk = dialect === "postgres"
211
+ ? "id SERIAL PRIMARY KEY"
212
+ : "id INTEGER PRIMARY KEY AUTOINCREMENT";
213
+ return `import type { DatabaseAdapter } from "jsxserve";
214
+
215
+ export async function up(adapter: DatabaseAdapter): Promise<void> {
216
+ await adapter.raw(\`
217
+ CREATE TABLE IF NOT EXISTS oauth_accounts (
218
+ ${pk},
219
+ user_id INTEGER NOT NULL,
220
+ provider TEXT NOT NULL,
221
+ provider_user_id TEXT NOT NULL,
222
+ email TEXT NOT NULL,
223
+ UNIQUE(provider, provider_user_id)
224
+ )
225
+ \`);
226
+ }
227
+
228
+ export async function down(adapter: DatabaseAdapter): Promise<void> {
229
+ await adapter.raw("DROP TABLE IF EXISTS oauth_accounts");
230
+ }
231
+ `;
232
+ }
233
+ export function refreshTokenMigrationTemplate(dialect) {
234
+ const pk = dialect === "postgres"
235
+ ? "id SERIAL PRIMARY KEY"
236
+ : "id INTEGER PRIMARY KEY AUTOINCREMENT";
237
+ return `import type { DatabaseAdapter } from "jsxserve";
238
+
239
+ export async function up(adapter: DatabaseAdapter): Promise<void> {
240
+ await adapter.raw(\`
241
+ CREATE TABLE IF NOT EXISTS refresh_tokens (
242
+ ${pk},
243
+ user_id INTEGER NOT NULL,
244
+ token TEXT NOT NULL UNIQUE,
245
+ expires_at TEXT NOT NULL
246
+ )
247
+ \`);
248
+ }
249
+
250
+ export async function down(adapter: DatabaseAdapter): Promise<void> {
251
+ await adapter.raw("DROP TABLE IF EXISTS refresh_tokens");
252
+ }
253
+ `;
254
+ }
255
+ export function authMiddlewareTemplate() {
256
+ return `import { Middleware, type JsxpressRequest, type NextFunction } from "jsxserve";
257
+ import { Res } from "jsxserve";
258
+ import { verifyAccessToken } from "@/auth/jwt.js";
259
+
260
+ export class Auth extends Middleware {
261
+ async handle(req: JsxpressRequest, next: NextFunction): Promise<Response> {
262
+ const header = req.headers.get("authorization");
263
+ if (!header || !header.startsWith("Bearer ")) {
264
+ return Res.json({ error: "Unauthorized" }, { status: 401 });
265
+ }
266
+
267
+ try {
268
+ const token = header.slice(7);
269
+ const userId = await verifyAccessToken(token);
270
+ (req as any).userId = userId;
271
+ return next();
272
+ } catch {
273
+ return Res.json({ error: "Invalid token" }, { status: 401 });
274
+ }
275
+ }
276
+ }
277
+ `;
278
+ }
279
+ export function authRegisterControllerTemplate() {
280
+ return `import { DatabaseController, type JsxpressRequest } from "jsxserve";
281
+ import { Res, v } from "jsxserve";
282
+ import { hashPassword } from "@/auth/password.js";
283
+ import { signAccessToken, signRefreshToken } from "@/auth/jwt.js";
284
+ import { User } from "@/models/User.js";
285
+ import { RefreshToken } from "@/models/RefreshToken.js";
286
+
287
+ export class Register extends DatabaseController {
288
+ name = "register";
289
+
290
+ schema = v.object({
291
+ email: v.string().email(),
292
+ password: v.string().min(8),
293
+ name: v.string().min(1),
294
+ });
295
+
296
+ async post(req: JsxpressRequest) {
297
+ const body = await this.validate(req);
298
+
299
+ const existing = await this.db.findOne(User, { email: body.email });
300
+ if (existing) {
301
+ return Res.json({ error: "Email already registered" }, { status: 409 });
302
+ }
303
+
304
+ const passwordHash = await hashPassword(body.password);
305
+ const user = await this.db.create(User, {
306
+ email: body.email,
307
+ password_hash: passwordHash,
308
+ name: body.name,
309
+ created_at: new Date().toISOString(),
310
+ });
311
+
312
+ const accessToken = await signAccessToken(String(user.id));
313
+ const refreshToken = await signRefreshToken(String(user.id));
314
+
315
+ await this.db.create(RefreshToken, {
316
+ user_id: user.id,
317
+ token: refreshToken,
318
+ expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
319
+ });
320
+
321
+ return Res.json({ accessToken, refreshToken }, { status: 201 });
322
+ }
323
+ }
324
+ `;
325
+ }
326
+ export function authLoginControllerTemplate() {
327
+ return `import { DatabaseController, type JsxpressRequest } from "jsxserve";
328
+ import { Res, v } from "jsxserve";
329
+ import { verifyPassword } from "@/auth/password.js";
330
+ import { signAccessToken, signRefreshToken } from "@/auth/jwt.js";
331
+ import { User } from "@/models/User.js";
332
+ import { RefreshToken } from "@/models/RefreshToken.js";
333
+
334
+ export class Login extends DatabaseController {
335
+ name = "login";
336
+
337
+ schema = v.object({
338
+ email: v.string().email(),
339
+ password: v.string().min(1),
340
+ });
341
+
342
+ async post(req: JsxpressRequest) {
343
+ const body = await this.validate(req);
344
+
345
+ const user = await this.db.findOne(User, { email: body.email });
346
+ if (!user || !user.password_hash) {
347
+ return Res.json({ error: "Invalid credentials" }, { status: 401 });
348
+ }
349
+
350
+ const valid = await verifyPassword(user.password_hash as string, body.password);
351
+ if (!valid) {
352
+ return Res.json({ error: "Invalid credentials" }, { status: 401 });
353
+ }
354
+
355
+ const accessToken = await signAccessToken(String(user.id));
356
+ const refreshToken = await signRefreshToken(String(user.id));
357
+
358
+ await this.db.create(RefreshToken, {
359
+ user_id: user.id,
360
+ token: refreshToken,
361
+ expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
362
+ });
363
+
364
+ return Res.json({ accessToken, refreshToken });
365
+ }
366
+ }
367
+ `;
368
+ }
369
+ export function authRefreshControllerTemplate() {
370
+ return `import { DatabaseController, type JsxpressRequest } from "jsxserve";
371
+ import { Res, v } from "jsxserve";
372
+ import { signAccessToken, signRefreshToken } from "@/auth/jwt.js";
373
+ import { RefreshToken } from "@/models/RefreshToken.js";
374
+
375
+ export class Refresh extends DatabaseController {
376
+ name = "refresh";
377
+
378
+ schema = v.object({
379
+ refreshToken: v.string().min(1),
380
+ });
381
+
382
+ async post(req: JsxpressRequest) {
383
+ const body = await this.validate(req);
384
+
385
+ const stored = await this.db.findOne(RefreshToken, { token: body.refreshToken });
386
+ if (!stored) {
387
+ return Res.json({ error: "Invalid refresh token" }, { status: 401 });
388
+ }
389
+
390
+ if (new Date(stored.expires_at as string) < new Date()) {
391
+ await this.db.delete(RefreshToken, { id: stored.id });
392
+ return Res.json({ error: "Refresh token expired" }, { status: 401 });
393
+ }
394
+
395
+ // Token rotation: delete old, issue new pair
396
+ await this.db.delete(RefreshToken, { id: stored.id });
397
+
398
+ const userId = String(stored.user_id);
399
+ const accessToken = await signAccessToken(userId);
400
+ const refreshToken = await signRefreshToken(userId);
401
+
402
+ await this.db.create(RefreshToken, {
403
+ user_id: stored.user_id,
404
+ token: refreshToken,
405
+ expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
406
+ });
407
+
408
+ return Res.json({ accessToken, refreshToken });
409
+ }
410
+ }
411
+ `;
412
+ }
413
+ export function authLogoutControllerTemplate() {
414
+ return `import { DatabaseController, type JsxpressRequest } from "jsxserve";
415
+ import { Res, v } from "jsxserve";
416
+ import { RefreshToken } from "@/models/RefreshToken.js";
417
+
418
+ export class Logout extends DatabaseController {
419
+ name = "logout";
420
+
421
+ schema = v.object({
422
+ refreshToken: v.string().min(1),
423
+ });
424
+
425
+ async post(req: JsxpressRequest) {
426
+ const body = await this.validate(req);
427
+ await this.db.delete(RefreshToken, { token: body.refreshToken });
428
+ return Res.noContent();
429
+ }
430
+ }
431
+ `;
432
+ }
433
+ export function authOAuthControllerTemplate(provider) {
434
+ const name = capitalize(provider);
435
+ return `import { Controller, type JsxpressRequest } from "jsxserve";
436
+ import { Res } from "jsxserve";
437
+ import { get${name}AuthUrl } from "@/auth/oauth/${provider}.js";
438
+
439
+ export class ${name}Auth extends Controller {
440
+ name = "${provider}-auth";
441
+
442
+ get(req: JsxpressRequest) {
443
+ return Res.redirect(get${name}AuthUrl());
444
+ }
445
+ }
446
+ `;
447
+ }
448
+ export function authOAuthCallbackControllerTemplate(provider) {
449
+ const name = capitalize(provider);
450
+ return `import { DatabaseController, type JsxpressRequest } from "jsxserve";
451
+ import { Res } from "jsxserve";
452
+ import { get${name}User } from "@/auth/oauth/${provider}.js";
453
+ import { signAccessToken, signRefreshToken } from "@/auth/jwt.js";
454
+ import { User } from "@/models/User.js";
455
+ import { OAuthAccount } from "@/models/OAuthAccount.js";
456
+ import { RefreshToken } from "@/models/RefreshToken.js";
457
+
458
+ export class ${name}Callback extends DatabaseController {
459
+ name = "${provider}-callback";
460
+
461
+ async get(req: JsxpressRequest) {
462
+ const url = new URL(req.url);
463
+ const code = url.searchParams.get("code");
464
+ if (!code) {
465
+ return Res.json({ error: "Missing code parameter" }, { status: 400 });
466
+ }
467
+
468
+ const providerUser = await get${name}User(code);
469
+
470
+ // Check if OAuth account already linked
471
+ let oauthAccount = await this.db.findOne(OAuthAccount, {
472
+ provider: "${provider}",
473
+ provider_user_id: providerUser.id,
474
+ });
475
+
476
+ let userId: number;
477
+
478
+ if (oauthAccount) {
479
+ userId = oauthAccount.user_id as number;
480
+ } else {
481
+ // Check if user exists with same email
482
+ let user = await this.db.findOne(User, { email: providerUser.email });
483
+
484
+ if (!user) {
485
+ user = await this.db.create(User, {
486
+ email: providerUser.email,
487
+ name: providerUser.name,
488
+ created_at: new Date().toISOString(),
489
+ });
490
+ }
491
+
492
+ userId = user.id as number;
493
+
494
+ await this.db.create(OAuthAccount, {
495
+ user_id: userId,
496
+ provider: "${provider}",
497
+ provider_user_id: providerUser.id,
498
+ email: providerUser.email,
499
+ });
500
+ }
501
+
502
+ const accessToken = await signAccessToken(String(userId));
503
+ const refreshToken = await signRefreshToken(String(userId));
504
+
505
+ await this.db.create(RefreshToken, {
506
+ user_id: userId,
507
+ token: refreshToken,
508
+ expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
509
+ });
510
+
511
+ return Res.json({ accessToken, refreshToken });
512
+ }
513
+ }
514
+ `;
515
+ }
516
+ //# sourceMappingURL=auth.js.map