create-sprint 0.0.48 → 0.0.54

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.
@@ -1,7 +1,7 @@
1
1
  import * as crypto from "node:crypto";
2
2
  export function generateJWTKeys() {
3
- const keys = crypto.generateKeyPairSync("ec", {
4
- namedCurve: "prime256v1",
3
+ const keys = crypto.generateKeyPairSync("rsa", {
4
+ modulusLength: 4096,
5
5
  publicKeyEncoding: { type: "spki", format: "pem" },
6
6
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
7
7
  });
@@ -130,22 +130,24 @@ export function getHomeRoute(language) {
130
130
  if (language === "typescript") {
131
131
  return `import { Router } from "sprint-es";
132
132
  import { homeSchema } from "@/schemas/home";
133
- import { homeController } from "@/controllers/home";
133
+ import { homeController, jwtValidateController } from "@/controllers/home";
134
134
 
135
135
  const router = Router();
136
136
 
137
137
  router.get("/", homeSchema, homeController);
138
+ router.post("/jwt/validate", jwtValidateController);
138
139
 
139
140
  export default router;
140
141
  `;
141
142
  }
142
143
  return `import { Router } from "sprint-es";
143
144
  import { homeSchema } from "../schemas/home.js";
144
- import { homeController } from "../controllers/home.js";
145
+ import { homeController, jwtValidateController } from "../controllers/home.js";
145
146
 
146
147
  const router = Router();
147
148
 
148
149
  router.get("/", homeSchema, homeController);
150
+ router.post("/jwt/validate", jwtValidateController);
149
151
 
150
152
  export default router;
151
153
  `;
@@ -154,67 +156,109 @@ export function getAdminRoute(language) {
154
156
  if (language === "typescript") {
155
157
  return `import { Router } from "sprint-es";
156
158
  import { adminSchema, jwtGenerateSchema } from "@/schemas/admin";
157
- import { adminController, adminUsersController, jwtGenerateController, jwtValidateController } from "@/controllers/admin";
159
+ import { adminController, adminUsersController, jwtGenerateController } from "@/controllers/admin";
158
160
 
159
161
  const router = Router();
160
162
 
161
163
  router.get("/", adminSchema, adminController);
162
164
  router.get("/users", adminSchema, adminUsersController);
163
165
  router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
164
- router.post("/jwt/validate", jwtValidateController);
165
166
 
166
167
  export default router;
167
168
  `;
168
169
  }
169
170
  return `import { Router } from "sprint-es";
170
171
  import { adminSchema, jwtGenerateSchema } from "../schemas/admin.js";
171
- import { adminController, adminUsersController, jwtGenerateController, jwtValidateController } from "../controllers/admin.js";
172
+ import { adminController, adminUsersController, jwtGenerateController } from "../controllers/admin.js";
172
173
 
173
174
  const router = Router();
174
175
 
175
176
  router.get("/", adminSchema, adminController);
176
177
  router.get("/users", adminSchema, adminUsersController);
177
178
  router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
178
- router.post("/jwt/validate", jwtValidateController);
179
179
 
180
180
  export default router;
181
181
  `;
182
182
  }
183
183
  export function getHomeController(language) {
184
184
  if (language === "typescript") {
185
- return `import { Handler } from "sprint-es";
185
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
186
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
186
187
 
187
- export const homeController: Handler = (req, res) => {
188
+ export const homeController: Handler = (req: SprintRequest, res: SprintResponse) => {
188
189
  res.json({
189
190
  message: "Hello World",
190
191
  status: "ok"
191
192
  });
192
193
  };
194
+
195
+ export const jwtValidateController: Handler = (req: SprintRequest, res: SprintResponse) => {
196
+ const token = req.sprint?.getAuthorization() || req.headers.authorization;
197
+
198
+ if (!token) {
199
+ return res.status(401).json({ error: "No token provided" });
200
+ }
201
+
202
+ try {
203
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
204
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
205
+
206
+ if (!decoded) {
207
+ return res.status(401).json({ error: "Invalid token" });
208
+ }
209
+
210
+ res.json({ valid: true, payload: decoded });
211
+ } catch (error) {
212
+ return res.status(500).json({ error: "JWT not configured" });
213
+ }
214
+ };
193
215
  `;
194
216
  }
195
- return `import { Handler } from "sprint-es";
217
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
218
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
196
219
 
197
- export const homeController = (req, res) => {
220
+ export const homeController = (req: SprintRequest, res: SprintResponse) => {
198
221
  res.json({
199
222
  message: "Hello World",
200
223
  status: "ok"
201
224
  });
202
225
  };
226
+
227
+ export const jwtValidateController = (req: SprintRequest, res: SprintResponse) => {
228
+ const token = req.sprint?.getAuthorization() || req.headers.authorization;
229
+
230
+ if (!token) {
231
+ return res.status(401).json({ error: "No token provided" });
232
+ }
233
+
234
+ try {
235
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
236
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
237
+
238
+ if (!decoded) {
239
+ return res.status(401).json({ error: "Invalid token" });
240
+ }
241
+
242
+ res.json({ valid: true, payload: decoded });
243
+ } catch (error) {
244
+ return res.status(500).json({ error: "JWT not configured" });
245
+ }
246
+ };
203
247
  `;
204
248
  }
205
249
  export function getAdminController(language) {
206
250
  if (language === "typescript") {
207
- return `import { Handler } from "sprint-es";
208
- import { signEncrypted, verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
251
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
252
+ import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
209
253
 
210
- export const adminController: Handler = (req, res) => {
254
+ export const adminController: Handler = (req: SprintRequest, res: SprintResponse) => {
211
255
  res.json({
212
256
  message: "Admin Dashboard",
213
257
  status: "ok"
214
258
  });
215
259
  };
216
260
 
217
- export const adminUsersController: Handler = (req, res) => {
261
+ export const adminUsersController: Handler = (req: SprintRequest, res: SprintResponse) => {
218
262
  res.json({
219
263
  users: [
220
264
  { id: 1, name: "John Doe", role: "admin" },
@@ -223,52 +267,31 @@ export const adminUsersController: Handler = (req, res) => {
223
267
  });
224
268
  };
225
269
 
226
- export const jwtGenerateController: Handler = (req, res) => {
270
+ export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintResponse) => {
227
271
  const { userId, role } = req.body || {};
228
272
 
229
273
  try {
230
274
  const { privateKey } = getJwtFromEnv();
231
275
  const payload = { userId, role: role || "user" };
232
- const token = signEncrypted(payload, privateKey, { expiresIn: "1h" });
276
+ const token = signEncrypted(payload, privateKey, encryptionSecret, { expiresIn: "1h" });
233
277
  res.json({ token });
234
278
  } catch (error) {
235
279
  return res.status(500).json({ error: "JWT not configured" });
236
280
  }
237
281
  };
238
-
239
- export const jwtValidateController: Handler = (req, res) => {
240
- const token = req.sprint?.getAuthorization() || req.headers.authorization;
241
-
242
- if (!token) {
243
- return res.status(401).json({ error: "No token provided" });
244
- }
245
-
246
- try {
247
- const { publicKey } = getJwtFromEnv();
248
- const decoded = verifyEncrypted(token, publicKey);
249
-
250
- if (!decoded) {
251
- return res.status(401).json({ error: "Invalid token" });
252
- }
253
-
254
- res.json({ valid: true, payload: decoded });
255
- } catch (error) {
256
- return res.status(500).json({ error: "JWT not configured" });
257
- }
258
- };
259
282
  `;
260
283
  }
261
- return `import { Handler } from "sprint-es";
262
- import { signEncrypted, verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
284
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
285
+ import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
263
286
 
264
- export const adminController = (req, res) => {
287
+ export const adminController = (req: SprintRequest, res: SprintResponse) => {
265
288
  res.json({
266
289
  message: "Admin Dashboard",
267
290
  status: "ok"
268
291
  });
269
292
  };
270
293
 
271
- export const adminUsersController = (req, res) => {
294
+ export const adminUsersController = (req: SprintRequest, res: SprintResponse) => {
272
295
  res.json({
273
296
  users: [
274
297
  { id: 1, name: "John Doe", role: "admin" },
@@ -277,39 +300,18 @@ export const adminUsersController = (req, res) => {
277
300
  });
278
301
  };
279
302
 
280
- export const jwtGenerateController = (req, res) => {
303
+ export const jwtGenerateController = (req: SprintRequest, res: SprintResponse) => {
281
304
  const { userId, role } = req.body || {};
282
305
 
283
306
  try {
284
- const { privateKey } = getJwtFromEnv();
307
+ const { privateKey, encryptionSecret } = getJwtFromEnv();
285
308
  const payload = { userId, role: role || "user" };
286
- const token = signEncrypted(payload, privateKey, { expiresIn: "1h" });
309
+ const token = signEncrypted(payload, privateKey, encryptionSecret, { expiresIn: "1h" });
287
310
  res.json({ token });
288
311
  } catch (error) {
289
312
  return res.status(500).json({ error: "JWT not configured" });
290
313
  }
291
314
  };
292
-
293
- export const jwtValidateController = (req, res) => {
294
- const token = req.sprint?.getAuthorization() || req.headers.authorization;
295
-
296
- if (!token) {
297
- return res.status(401).json({ error: "No token provided" });
298
- }
299
-
300
- try {
301
- const { publicKey } = getJwtFromEnv();
302
- const decoded = verifyEncrypted(token, publicKey);
303
-
304
- if (!decoded) {
305
- return res.status(401).json({ error: "Invalid token" });
306
- }
307
-
308
- res.json({ valid: true, payload: decoded });
309
- } catch (error) {
310
- return res.status(500).json({ error: "JWT not configured" });
311
- }
312
- };
313
315
  `;
314
316
  }
315
317
  export function getHomeSchema(language) {
@@ -366,26 +368,21 @@ export const jwtGenerateSchema = defineRouteSchema({
366
368
  });
367
369
  `;
368
370
  }
369
- export function getAuthMiddleware(language) {
371
+ export function getInternalAuthMiddleware(language) {
370
372
  if (language === "typescript") {
371
373
  return `import { defineMiddleware } from "sprint-es";
372
374
 
373
375
  export default defineMiddleware({
374
- name: "auth",
376
+ name: "adminAuth",
375
377
  priority: 10,
376
378
  include: "/admin/**",
377
379
  handler: (req, res, next) => {
378
380
  const auth = req.sprint.getAuthorization();
379
-
380
- if (!auth) {
381
- return res.status(401).json({ error: "No authorization header" });
382
- }
381
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
383
382
 
384
383
  const token = auth.replace("Bearer ", "");
385
384
 
386
- if (token !== "admin-token") {
387
- return res.status(403).json({ error: "Invalid token" });
388
- }
385
+ if (token !== "admin-token") return res.status(403).json({ error: "Invalid token" });
389
386
 
390
387
  next();
391
388
  }
@@ -395,21 +392,72 @@ export default defineMiddleware({
395
392
  return `import { defineMiddleware } from "sprint-es";
396
393
 
397
394
  export default defineMiddleware({
398
- name: "auth",
395
+ name: "adminAuth",
399
396
  priority: 10,
400
397
  include: "/admin/**",
401
398
  handler: (req, res, next) => {
402
399
  const auth = req.sprint.getAuthorization();
400
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
401
+
402
+ const token = auth.replace("Bearer ", "");
403
403
 
404
- if (!auth) {
405
- return res.status(401).json({ error: "No authorization header" });
406
- }
404
+ if (token !== "admin-token") return res.status(403).json({ error: "Invalid token" });
405
+
406
+ next();
407
+ }
408
+ });
409
+ `;
410
+ }
411
+ export function getUserAuthMiddleware(language) {
412
+ if (language === "typescript") {
413
+ return `import { defineMiddleware } from "sprint-es";
414
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
415
+
416
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
417
+
418
+ export default defineMiddleware({
419
+ name: "userAuth",
420
+ priority: 10,
421
+ include: "/**",
422
+ exclude: "/admin/**",
423
+ handler: (req, res, next) => {
424
+ const auth = req.sprint.getAuthorization();
425
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
407
426
 
408
427
  const token = auth.replace("Bearer ", "");
409
428
 
410
- if (token !== "admin-token") {
411
- return res.status(403).json({ error: "Invalid token" });
412
- }
429
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);cd .
430
+
431
+ if (!decoded) return res.status(403).json({ error: "Invalid token" });
432
+
433
+ req.custom.user = decoded;
434
+
435
+ next();
436
+ }
437
+ });
438
+ `;
439
+ }
440
+ return `import { defineMiddleware } from "sprint-es";
441
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
442
+
443
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
444
+
445
+ export default defineMiddleware({
446
+ name: "userAuth",
447
+ priority: 10,
448
+ include: "/**",
449
+ exclude: "/admin/**",
450
+ handler: (req, res, next) => {
451
+ const auth = req.sprint.getAuthorization();
452
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
453
+
454
+ const token = auth.replace("Bearer ", "");
455
+
456
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
457
+
458
+ if (!decoded) return res.status(403).json({ error: "Invalid token" });
459
+
460
+ req.custom.user = decoded;
413
461
 
414
462
  next();
415
463
  }
@@ -614,6 +662,7 @@ export function getEnvDevelopment(telemetry) {
614
662
  PORT=5000
615
663
  JWT_PUBLIC_KEY='${keys.publicKey}'
616
664
  JWT_PRIVATE_KEY='${keys.privateKey}'
665
+ JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
617
666
  `;
618
667
  if (telemetry === "sentry" || telemetry === "glitchtip") {
619
668
  env += `
@@ -635,6 +684,7 @@ export function getEnvProduction(telemetry) {
635
684
  PORT=5000
636
685
  JWT_PUBLIC_KEY='${keys.publicKey}'
637
686
  JWT_PRIVATE_KEY='${keys.privateKey}'
687
+ JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
638
688
  `;
639
689
  if (telemetry === "sentry" || telemetry === "glitchtip") {
640
690
  env += `
package/dist/index.js CHANGED
@@ -2,9 +2,10 @@ import { spawn } from "child_process";
2
2
  import { existsSync } from "fs";
3
3
  import { mkdir, writeFile as fsWriteFile } from "fs/promises";
4
4
  import { join } from "path";
5
+ import color from "picocolors";
5
6
  import * as p from "@clack/prompts";
6
7
  import { validateProjectName } from "./validators.js";
7
- import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob } from "./generators.js";
8
+ import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getInternalAuthMiddleware, getUserAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob } from "./generators.js";
8
9
  export async function writeFile(path, content, options) {
9
10
  if (typeof content === "string")
10
11
  content = content.trimEnd();
@@ -14,6 +15,7 @@ export async function writeFile(path, content, options) {
14
15
  export async function runCLI(args) {
15
16
  const options = parseArgs(args);
16
17
  p.intro("Sprint — Quickly API Framework");
18
+ p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
17
19
  const config = await p.group({
18
20
  projectName: () => p.text({
19
21
  message: "Project name:",
@@ -49,12 +51,13 @@ export async function runCLI(args) {
49
51
  await createProject(config.projectName, config.language, config.telemetry, config.docker);
50
52
  s.stop("Project created");
51
53
  const installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
54
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
52
55
  if (installDeps) {
53
56
  const s2 = p.spinner();
54
57
  s2.start("Installing dependencies");
55
58
  try {
56
59
  await new Promise((resolve, reject) => {
57
- const child = spawn("npm", ["install"], {
60
+ const child = spawn(npmCmd, ["install"], {
58
61
  cwd: targetDir,
59
62
  stdio: "pipe"
60
63
  });
@@ -64,6 +67,7 @@ export async function runCLI(args) {
64
67
  else
65
68
  reject(new Error(`npm install exited with code ${code}`));
66
69
  });
70
+ child.on("error", reject);
67
71
  });
68
72
  s2.stop("Dependencies installed");
69
73
  }
@@ -154,7 +158,8 @@ async function createProject(projectName, language, telemetry, useDocker) {
154
158
  await writeFile(join(srcDir, "routes", "admin." + (language === "typescript" ? "ts" : "js")), getAdminRoute(language));
155
159
  await writeFile(join(srcDir, "controllers", "home." + (language === "typescript" ? "ts" : "js")), getHomeController(language));
156
160
  await writeFile(join(srcDir, "controllers", "admin." + (language === "typescript" ? "ts" : "js")), getAdminController(language));
157
- await writeFile(join(srcDir, "middlewares", "auth." + (language === "typescript" ? "ts" : "js")), getAuthMiddleware(language));
161
+ await writeFile(join(srcDir, "middlewares", "auth.internal." + (language === "typescript" ? "ts" : "js")), getInternalAuthMiddleware(language));
162
+ await writeFile(join(srcDir, "middlewares", "auth.user." + (language === "typescript" ? "ts" : "js")), getUserAuthMiddleware(language));
158
163
  await writeFile(join(srcDir, "schemas", "home." + (language === "typescript" ? "ts" : "js")), getHomeSchema(language));
159
164
  await writeFile(join(srcDir, "schemas", "admin." + (language === "typescript" ? "ts" : "js")), getAdminSchema(language));
160
165
  await writeFile(join(srcDir, "cronjobs", "example." + (language === "typescript" ? "ts" : "js")), getExampleCronJob(language));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sprint",
3
- "version": "0.0.48",
3
+ "version": "0.0.54",
4
4
  "description": "Create a new Sprint API project",
5
5
  "type": "module",
6
6
  "bin": {
package/src/generators.ts CHANGED
@@ -6,8 +6,8 @@ export interface JWTKeys {
6
6
  }
7
7
 
8
8
  export function generateJWTKeys(): JWTKeys {
9
- const keys = crypto.generateKeyPairSync("ec", {
10
- namedCurve: "prime256v1",
9
+ const keys = crypto.generateKeyPairSync("rsa", {
10
+ modulusLength: 4096,
11
11
  publicKeyEncoding: { type: "spki", format: "pem" },
12
12
  privateKeyEncoding: { type: "pkcs8", format: "pem" }
13
13
  }) as unknown as { publicKey: string; privateKey: string };
@@ -146,22 +146,24 @@ export function getHomeRoute(language: string) {
146
146
  if (language === "typescript") {
147
147
  return `import { Router } from "sprint-es";
148
148
  import { homeSchema } from "@/schemas/home";
149
- import { homeController } from "@/controllers/home";
149
+ import { homeController, jwtValidateController } from "@/controllers/home";
150
150
 
151
151
  const router = Router();
152
152
 
153
153
  router.get("/", homeSchema, homeController);
154
+ router.post("/jwt/validate", jwtValidateController);
154
155
 
155
156
  export default router;
156
157
  `;
157
158
  }
158
159
  return `import { Router } from "sprint-es";
159
160
  import { homeSchema } from "../schemas/home.js";
160
- import { homeController } from "../controllers/home.js";
161
+ import { homeController, jwtValidateController } from "../controllers/home.js";
161
162
 
162
163
  const router = Router();
163
164
 
164
165
  router.get("/", homeSchema, homeController);
166
+ router.post("/jwt/validate", jwtValidateController);
165
167
 
166
168
  export default router;
167
169
  `;
@@ -171,28 +173,26 @@ export function getAdminRoute(language: string) {
171
173
  if (language === "typescript") {
172
174
  return `import { Router } from "sprint-es";
173
175
  import { adminSchema, jwtGenerateSchema } from "@/schemas/admin";
174
- import { adminController, adminUsersController, jwtGenerateController, jwtValidateController } from "@/controllers/admin";
176
+ import { adminController, adminUsersController, jwtGenerateController } from "@/controllers/admin";
175
177
 
176
178
  const router = Router();
177
179
 
178
180
  router.get("/", adminSchema, adminController);
179
181
  router.get("/users", adminSchema, adminUsersController);
180
182
  router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
181
- router.post("/jwt/validate", jwtValidateController);
182
183
 
183
184
  export default router;
184
185
  `;
185
186
  }
186
187
  return `import { Router } from "sprint-es";
187
188
  import { adminSchema, jwtGenerateSchema } from "../schemas/admin.js";
188
- import { adminController, adminUsersController, jwtGenerateController, jwtValidateController } from "../controllers/admin.js";
189
+ import { adminController, adminUsersController, jwtGenerateController } from "../controllers/admin.js";
189
190
 
190
191
  const router = Router();
191
192
 
192
193
  router.get("/", adminSchema, adminController);
193
194
  router.get("/users", adminSchema, adminUsersController);
194
195
  router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
195
- router.post("/jwt/validate", jwtValidateController);
196
196
 
197
197
  export default router;
198
198
  `;
@@ -200,40 +200,84 @@ export default router;
200
200
 
201
201
  export function getHomeController(language: string) {
202
202
  if (language === "typescript") {
203
- return `import { Handler } from "sprint-es";
203
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
204
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
204
205
 
205
- export const homeController: Handler = (req, res) => {
206
+ export const homeController: Handler = (req: SprintRequest, res: SprintResponse) => {
206
207
  res.json({
207
208
  message: "Hello World",
208
209
  status: "ok"
209
210
  });
210
211
  };
212
+
213
+ export const jwtValidateController: Handler = (req: SprintRequest, res: SprintResponse) => {
214
+ const token = req.sprint?.getAuthorization() || req.headers.authorization;
215
+
216
+ if (!token) {
217
+ return res.status(401).json({ error: "No token provided" });
218
+ }
219
+
220
+ try {
221
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
222
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
223
+
224
+ if (!decoded) {
225
+ return res.status(401).json({ error: "Invalid token" });
226
+ }
227
+
228
+ res.json({ valid: true, payload: decoded });
229
+ } catch (error) {
230
+ return res.status(500).json({ error: "JWT not configured" });
231
+ }
232
+ };
211
233
  `;
212
234
  }
213
- return `import { Handler } from "sprint-es";
235
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
236
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
214
237
 
215
- export const homeController = (req, res) => {
238
+ export const homeController = (req: SprintRequest, res: SprintResponse) => {
216
239
  res.json({
217
240
  message: "Hello World",
218
241
  status: "ok"
219
242
  });
220
243
  };
244
+
245
+ export const jwtValidateController = (req: SprintRequest, res: SprintResponse) => {
246
+ const token = req.sprint?.getAuthorization() || req.headers.authorization;
247
+
248
+ if (!token) {
249
+ return res.status(401).json({ error: "No token provided" });
250
+ }
251
+
252
+ try {
253
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
254
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
255
+
256
+ if (!decoded) {
257
+ return res.status(401).json({ error: "Invalid token" });
258
+ }
259
+
260
+ res.json({ valid: true, payload: decoded });
261
+ } catch (error) {
262
+ return res.status(500).json({ error: "JWT not configured" });
263
+ }
264
+ };
221
265
  `;
222
266
  }
223
267
 
224
268
  export function getAdminController(language: string) {
225
269
  if (language === "typescript") {
226
- return `import { Handler } from "sprint-es";
227
- import { signEncrypted, verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
270
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
271
+ import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
228
272
 
229
- export const adminController: Handler = (req, res) => {
273
+ export const adminController: Handler = (req: SprintRequest, res: SprintResponse) => {
230
274
  res.json({
231
275
  message: "Admin Dashboard",
232
276
  status: "ok"
233
277
  });
234
278
  };
235
279
 
236
- export const adminUsersController: Handler = (req, res) => {
280
+ export const adminUsersController: Handler = (req: SprintRequest, res: SprintResponse) => {
237
281
  res.json({
238
282
  users: [
239
283
  { id: 1, name: "John Doe", role: "admin" },
@@ -242,52 +286,31 @@ export const adminUsersController: Handler = (req, res) => {
242
286
  });
243
287
  };
244
288
 
245
- export const jwtGenerateController: Handler = (req, res) => {
289
+ export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintResponse) => {
246
290
  const { userId, role } = req.body || {};
247
291
 
248
292
  try {
249
293
  const { privateKey } = getJwtFromEnv();
250
294
  const payload = { userId, role: role || "user" };
251
- const token = signEncrypted(payload, privateKey, { expiresIn: "1h" });
295
+ const token = signEncrypted(payload, privateKey, encryptionSecret, { expiresIn: "1h" });
252
296
  res.json({ token });
253
297
  } catch (error) {
254
298
  return res.status(500).json({ error: "JWT not configured" });
255
299
  }
256
300
  };
257
-
258
- export const jwtValidateController: Handler = (req, res) => {
259
- const token = req.sprint?.getAuthorization() || req.headers.authorization;
260
-
261
- if (!token) {
262
- return res.status(401).json({ error: "No token provided" });
263
- }
264
-
265
- try {
266
- const { publicKey } = getJwtFromEnv();
267
- const decoded = verifyEncrypted(token, publicKey);
268
-
269
- if (!decoded) {
270
- return res.status(401).json({ error: "Invalid token" });
271
- }
272
-
273
- res.json({ valid: true, payload: decoded });
274
- } catch (error) {
275
- return res.status(500).json({ error: "JWT not configured" });
276
- }
277
- };
278
301
  `;
279
302
  }
280
- return `import { Handler } from "sprint-es";
281
- import { signEncrypted, verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
303
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
304
+ import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
282
305
 
283
- export const adminController = (req, res) => {
306
+ export const adminController = (req: SprintRequest, res: SprintResponse) => {
284
307
  res.json({
285
308
  message: "Admin Dashboard",
286
309
  status: "ok"
287
310
  });
288
311
  };
289
312
 
290
- export const adminUsersController = (req, res) => {
313
+ export const adminUsersController = (req: SprintRequest, res: SprintResponse) => {
291
314
  res.json({
292
315
  users: [
293
316
  { id: 1, name: "John Doe", role: "admin" },
@@ -296,40 +319,20 @@ export const adminUsersController = (req, res) => {
296
319
  });
297
320
  };
298
321
 
299
- export const jwtGenerateController = (req, res) => {
322
+ export const jwtGenerateController = (req: SprintRequest, res: SprintResponse) => {
300
323
  const { userId, role } = req.body || {};
301
324
 
302
325
  try {
303
- const { privateKey } = getJwtFromEnv();
326
+ const { privateKey, encryptionSecret } = getJwtFromEnv();
304
327
  const payload = { userId, role: role || "user" };
305
- const token = signEncrypted(payload, privateKey, { expiresIn: "1h" });
328
+ const token = signEncrypted(payload, privateKey, encryptionSecret, { expiresIn: "1h" });
306
329
  res.json({ token });
307
330
  } catch (error) {
308
331
  return res.status(500).json({ error: "JWT not configured" });
309
332
  }
310
333
  };
311
-
312
- export const jwtValidateController = (req, res) => {
313
- const token = req.sprint?.getAuthorization() || req.headers.authorization;
314
-
315
- if (!token) {
316
- return res.status(401).json({ error: "No token provided" });
317
- }
318
-
319
- try {
320
- const { publicKey } = getJwtFromEnv();
321
- const decoded = verifyEncrypted(token, publicKey);
322
-
323
- if (!decoded) {
324
- return res.status(401).json({ error: "Invalid token" });
325
- }
326
-
327
- res.json({ valid: true, payload: decoded });
328
- } catch (error) {
329
- return res.status(500).json({ error: "JWT not configured" });
330
- }
331
- };
332
334
  `;
335
+
333
336
  }
334
337
 
335
338
  export function getHomeSchema(language: string) {
@@ -388,26 +391,21 @@ export const jwtGenerateSchema = defineRouteSchema({
388
391
  `;
389
392
  }
390
393
 
391
- export function getAuthMiddleware(language: string) {
394
+ export function getInternalAuthMiddleware(language: string) {
392
395
  if (language === "typescript") {
393
396
  return `import { defineMiddleware } from "sprint-es";
394
397
 
395
398
  export default defineMiddleware({
396
- name: "auth",
399
+ name: "adminAuth",
397
400
  priority: 10,
398
401
  include: "/admin/**",
399
402
  handler: (req, res, next) => {
400
403
  const auth = req.sprint.getAuthorization();
401
-
402
- if (!auth) {
403
- return res.status(401).json({ error: "No authorization header" });
404
- }
404
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
405
405
 
406
406
  const token = auth.replace("Bearer ", "");
407
407
 
408
- if (token !== "admin-token") {
409
- return res.status(403).json({ error: "Invalid token" });
410
- }
408
+ if (token !== "admin-token") return res.status(403).json({ error: "Invalid token" });
411
409
 
412
410
  next();
413
411
  }
@@ -417,21 +415,73 @@ export default defineMiddleware({
417
415
  return `import { defineMiddleware } from "sprint-es";
418
416
 
419
417
  export default defineMiddleware({
420
- name: "auth",
418
+ name: "adminAuth",
421
419
  priority: 10,
422
420
  include: "/admin/**",
423
421
  handler: (req, res, next) => {
424
422
  const auth = req.sprint.getAuthorization();
423
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
424
+
425
+ const token = auth.replace("Bearer ", "");
425
426
 
426
- if (!auth) {
427
- return res.status(401).json({ error: "No authorization header" });
428
- }
427
+ if (token !== "admin-token") return res.status(403).json({ error: "Invalid token" });
428
+
429
+ next();
430
+ }
431
+ });
432
+ `;
433
+ }
434
+
435
+ export function getUserAuthMiddleware(language: string) {
436
+ if (language === "typescript") {
437
+ return `import { defineMiddleware } from "sprint-es";
438
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
439
+
440
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
441
+
442
+ export default defineMiddleware({
443
+ name: "userAuth",
444
+ priority: 10,
445
+ include: "/**",
446
+ exclude: "/admin/**",
447
+ handler: (req, res, next) => {
448
+ const auth = req.sprint.getAuthorization();
449
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
429
450
 
430
451
  const token = auth.replace("Bearer ", "");
431
452
 
432
- if (token !== "admin-token") {
433
- return res.status(403).json({ error: "Invalid token" });
434
- }
453
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);cd .
454
+
455
+ if (!decoded) return res.status(403).json({ error: "Invalid token" });
456
+
457
+ req.custom.user = decoded;
458
+
459
+ next();
460
+ }
461
+ });
462
+ `;
463
+ }
464
+ return `import { defineMiddleware } from "sprint-es";
465
+ import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
466
+
467
+ const { publicKey, encryptionSecret } = getJwtFromEnv();
468
+
469
+ export default defineMiddleware({
470
+ name: "userAuth",
471
+ priority: 10,
472
+ include: "/**",
473
+ exclude: "/admin/**",
474
+ handler: (req, res, next) => {
475
+ const auth = req.sprint.getAuthorization();
476
+ if (!auth) return res.status(401).json({ error: "No authorization header" });
477
+
478
+ const token = auth.replace("Bearer ", "");
479
+
480
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
481
+
482
+ if (!decoded) return res.status(403).json({ error: "Invalid token" });
483
+
484
+ req.custom.user = decoded;
435
485
 
436
486
  next();
437
487
  }
@@ -648,6 +698,7 @@ export function getEnvDevelopment(telemetry: string) {
648
698
  PORT=5000
649
699
  JWT_PUBLIC_KEY='${keys.publicKey}'
650
700
  JWT_PRIVATE_KEY='${keys.privateKey}'
701
+ JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
651
702
  `;
652
703
 
653
704
  if (telemetry === "sentry" || telemetry === "glitchtip") {
@@ -671,6 +722,7 @@ export function getEnvProduction(telemetry: string) {
671
722
  PORT=5000
672
723
  JWT_PUBLIC_KEY='${keys.publicKey}'
673
724
  JWT_PRIVATE_KEY='${keys.privateKey}'
725
+ JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
674
726
  `;
675
727
 
676
728
  if (telemetry === "sentry" || telemetry === "glitchtip") {
package/src/index.ts CHANGED
@@ -2,9 +2,10 @@ import { spawn } from "child_process";
2
2
  import { existsSync, writeFileSync } from "fs";
3
3
  import { mkdir, writeFile as fsWriteFile } from "fs/promises";
4
4
  import { join } from "path";
5
+ import color from "picocolors";
5
6
  import * as p from "@clack/prompts";
6
7
  import { validateProjectName } from "./validators.js";
7
- import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob } from "./generators.js";
8
+ import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getInternalAuthMiddleware, getUserAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob } from "./generators.js";
8
9
 
9
10
  export interface CLIOptions {
10
11
  projectName?: string;
@@ -25,6 +26,8 @@ export async function runCLI(args: string[]) {
25
26
 
26
27
  p.intro("Sprint — Quickly API Framework");
27
28
 
29
+ p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
30
+
28
31
  const config = await p.group(
29
32
  {
30
33
  projectName: () =>
@@ -78,13 +81,14 @@ export async function runCLI(args: string[]) {
78
81
  s.stop("Project created");
79
82
 
80
83
  const installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
84
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
81
85
 
82
86
  if (installDeps) {
83
87
  const s2 = p.spinner();
84
88
  s2.start("Installing dependencies");
85
89
  try {
86
90
  await new Promise<void>((resolve, reject) => {
87
- const child = spawn("npm", ["install"], {
91
+ const child = spawn(npmCmd, ["install"], {
88
92
  cwd: targetDir,
89
93
  stdio: "pipe"
90
94
  });
@@ -92,6 +96,7 @@ export async function runCLI(args: string[]) {
92
96
  if (code === 0) resolve();
93
97
  else reject(new Error(`npm install exited with code ${code}`));
94
98
  });
99
+ child.on("error", reject);
95
100
  });
96
101
  s2.stop("Dependencies installed");
97
102
  } catch {
@@ -197,7 +202,8 @@ async function createProject(
197
202
  await writeFile(join(srcDir, "controllers", "home." + (language === "typescript" ? "ts" : "js")), getHomeController(language));
198
203
  await writeFile(join(srcDir, "controllers", "admin." + (language === "typescript" ? "ts" : "js")), getAdminController(language));
199
204
 
200
- await writeFile(join(srcDir, "middlewares", "auth." + (language === "typescript" ? "ts" : "js")), getAuthMiddleware(language));
205
+ await writeFile(join(srcDir, "middlewares", "auth.internal." + (language === "typescript" ? "ts" : "js")), getInternalAuthMiddleware(language));
206
+ await writeFile(join(srcDir, "middlewares", "auth.user." + (language === "typescript" ? "ts" : "js")), getUserAuthMiddleware(language));
201
207
 
202
208
  await writeFile(join(srcDir, "schemas", "home." + (language === "typescript" ? "ts" : "js")), getHomeSchema(language));
203
209
  await writeFile(join(srcDir, "schemas", "admin." + (language === "typescript" ? "ts" : "js")), getAdminSchema(language));