skyguard-js 1.0.2 → 1.1.1
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/README.md +211 -54
- package/dist/app.d.ts +43 -7
- package/dist/app.js +60 -18
- package/dist/crypto/hasher.d.ts +91 -0
- package/dist/crypto/hasher.js +220 -0
- package/dist/crypto/index.d.ts +2 -0
- package/dist/crypto/index.js +12 -0
- package/dist/crypto/jwt.d.ts +34 -0
- package/dist/crypto/jwt.js +112 -0
- package/dist/exceptions/uploadException.d.ts +6 -0
- package/dist/exceptions/uploadException.js +15 -0
- package/dist/helpers/http.d.ts +1 -2
- package/dist/helpers/http.js +3 -5
- package/dist/http/logger.d.ts +2 -3
- package/dist/http/logger.js +2 -2
- package/dist/http/nodeNativeHttp.d.ts +0 -2
- package/dist/http/nodeNativeHttp.js +8 -16
- package/dist/http/request.d.ts +29 -46
- package/dist/http/request.js +40 -62
- package/dist/http/response.d.ts +10 -9
- package/dist/http/response.js +43 -32
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -1
- package/dist/middlewares/auth.d.ts +34 -0
- package/dist/middlewares/auth.js +57 -0
- package/dist/middlewares/cors.js +7 -9
- package/dist/middlewares/index.d.ts +1 -0
- package/dist/middlewares/index.js +3 -1
- package/dist/middlewares/session.js +1 -1
- package/dist/parsers/jsonParser.js +2 -1
- package/dist/parsers/multipartParser.d.ts +12 -1
- package/dist/parsers/multipartParser.js +45 -14
- package/dist/parsers/parserInterface.d.ts +28 -2
- package/dist/parsers/parserInterface.js +14 -0
- package/dist/parsers/textParser.js +3 -2
- package/dist/parsers/urlEncodedParser.js +2 -1
- package/dist/parsers/xmlParser.js +3 -2
- package/dist/routing/router.js +18 -10
- package/dist/sessions/index.d.ts +1 -1
- package/dist/static/fileDownload.d.ts +4 -2
- package/dist/static/fileDownload.js +1 -6
- package/dist/static/fileStaticHandler.js +1 -1
- package/dist/storage/storage.d.ts +118 -0
- package/dist/storage/storage.js +178 -0
- package/dist/storage/types.d.ts +128 -0
- package/dist/storage/types.js +31 -0
- package/dist/storage/uploader.d.ts +196 -0
- package/dist/storage/uploader.js +370 -0
- package/dist/types/index.d.ts +0 -13
- package/dist/validators/index.d.ts +0 -1
- package/dist/validators/index.js +1 -3
- package/dist/validators/rules/arrayRule.d.ts +16 -0
- package/dist/validators/rules/arrayRule.js +66 -0
- package/dist/validators/rules/booleanRule.js +2 -2
- package/dist/validators/rules/index.d.ts +2 -1
- package/dist/validators/rules/index.js +5 -3
- package/dist/validators/rules/literalRule.d.ts +7 -0
- package/dist/validators/rules/literalRule.js +18 -0
- package/dist/validators/rules/numberRule.d.ts +1 -0
- package/dist/validators/rules/numberRule.js +2 -0
- package/dist/validators/rules/stringRule.d.ts +57 -1
- package/dist/validators/rules/stringRule.js +108 -6
- package/dist/validators/validationRule.d.ts +17 -15
- package/dist/validators/validationRule.js +20 -12
- package/dist/validators/validationSchema.d.ts +52 -112
- package/dist/validators/validationSchema.js +113 -144
- package/dist/views/engineTemplate.d.ts +56 -0
- package/dist/views/engineTemplate.js +64 -0
- package/package.json +11 -11
- package/dist/exceptions/index.d.ts +0 -5
- package/dist/exceptions/index.js +0 -16
- package/dist/helpers/app.d.ts +0 -4
- package/dist/helpers/app.js +0 -12
- package/dist/helpers/index.d.ts +0 -1
- package/dist/helpers/index.js +0 -9
- package/dist/validators/rules/emailRule.d.ts +0 -12
- package/dist/validators/rules/emailRule.js +0 -24
- package/dist/views/helpersTemplate.d.ts +0 -104
- package/dist/views/helpersTemplate.js +0 -186
- package/dist/views/index.d.ts +0 -4
- package/dist/views/index.js +0 -9
- package/dist/views/raptorEngine.d.ts +0 -127
- package/dist/views/raptorEngine.js +0 -165
- package/dist/views/templateEngine.d.ts +0 -80
- package/dist/views/templateEngine.js +0 -204
- package/dist/views/view.d.ts +0 -55
- package/dist/views/view.js +0 -2
package/README.md
CHANGED
|
@@ -26,11 +26,20 @@ At its current stage, the framework focuses on **routing**, **internal architect
|
|
|
26
26
|
- Request / Response abstractions
|
|
27
27
|
- Declarative data validation
|
|
28
28
|
- Simple template engine with layouts and helpers
|
|
29
|
+
- Built-in HTTP exceptions
|
|
30
|
+
- Password hashing and JWT token generation
|
|
31
|
+
- CORS middleware
|
|
32
|
+
- File uploads (via middleware)
|
|
29
33
|
- Static file serving
|
|
30
34
|
- Session handling (via middleware)
|
|
31
35
|
|
|
32
36
|
---
|
|
33
37
|
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> It is recommended to develop with `TypeScript` for a more secure and efficient development process; the framework already has native support for `TypeScript` and includes the necessary types.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
34
43
|
## 📦 Installation
|
|
35
44
|
|
|
36
45
|
```bash
|
|
@@ -63,11 +72,11 @@ Routes are registered using HTTP methods on the `app` instance.
|
|
|
63
72
|
|
|
64
73
|
```ts
|
|
65
74
|
app.get("/posts/{id}", (request: Request) => {
|
|
66
|
-
return Response.json(request.
|
|
75
|
+
return Response.json(request.params);
|
|
67
76
|
});
|
|
68
77
|
|
|
69
78
|
app.post("/posts", (request: Request) => {
|
|
70
|
-
return Response.json(request.
|
|
79
|
+
return Response.json(request.data);
|
|
71
80
|
});
|
|
72
81
|
```
|
|
73
82
|
|
|
@@ -99,7 +108,7 @@ const authMiddleware = async (
|
|
|
99
108
|
request: Request,
|
|
100
109
|
next: RouteHandler,
|
|
101
110
|
): Promise<Response> => {
|
|
102
|
-
if (request.
|
|
111
|
+
if (request.headers["authorization"] !== "secret") {
|
|
103
112
|
return Response.json({ message: "Unauthorized" }).setStatus(401);
|
|
104
113
|
}
|
|
105
114
|
|
|
@@ -121,37 +130,53 @@ app.get("/secure", () => Response.json({ secure: true }), [authMiddleware]);
|
|
|
121
130
|
|
|
122
131
|
---
|
|
123
132
|
|
|
124
|
-
##
|
|
133
|
+
## 🌐 CORS Middleware
|
|
134
|
+
|
|
135
|
+
To enable CORS, use the built-in `cors` middleware.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { cors } from "skyguard-js/middlewares";
|
|
139
|
+
|
|
140
|
+
app.middlewares([
|
|
141
|
+
cors({
|
|
142
|
+
origin: ["http://localhost:3000", "https://myapp.com"],
|
|
143
|
+
methods: ["GET", "POST"],
|
|
144
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
145
|
+
credentials: true,
|
|
146
|
+
}),
|
|
147
|
+
]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 📌 Static Files
|
|
125
153
|
|
|
126
|
-
To serve static files, use the `
|
|
154
|
+
To serve static files, use the application's `staticFiles` method with the directory path. The name of the folder will determine the initial route prefix.
|
|
127
155
|
|
|
128
156
|
```ts
|
|
129
157
|
import { join } from "node:path";
|
|
130
158
|
|
|
131
|
-
app.
|
|
159
|
+
app.staticFiles(join(__dirname, "..", "static"));
|
|
160
|
+
|
|
161
|
+
// Route http://localhost:3000/static/style.css will serve the file located at ./static/style.css
|
|
132
162
|
```
|
|
133
163
|
|
|
134
164
|
---
|
|
135
165
|
|
|
136
|
-
##
|
|
166
|
+
## ⛔ Data Validation
|
|
137
167
|
|
|
138
168
|
Skyguard.js provides a **declarative validation system** using schemas.
|
|
139
169
|
|
|
140
170
|
```ts
|
|
141
|
-
import {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
.
|
|
145
|
-
.required(
|
|
146
|
-
.
|
|
147
|
-
.
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
.field("age")
|
|
151
|
-
.number({ min: 18, max: 99 })
|
|
152
|
-
.field("active")
|
|
153
|
-
.boolean()
|
|
154
|
-
.build();
|
|
171
|
+
import { validator } from "skyguard-js/validation";
|
|
172
|
+
|
|
173
|
+
const userSchema = validator.schema({
|
|
174
|
+
name: validator.string({ maxLength: 60 }),
|
|
175
|
+
email: validator.email().required(),
|
|
176
|
+
age: validator.number({ min: 18 }),
|
|
177
|
+
active: validator.boolean().required(),
|
|
178
|
+
birthdate: validator.date({ max: new Date() }),
|
|
179
|
+
});
|
|
155
180
|
|
|
156
181
|
app.post("/users", (request: Request) => {
|
|
157
182
|
const validatedData = request.validateData(userSchema);
|
|
@@ -172,62 +197,194 @@ Validation is:
|
|
|
172
197
|
|
|
173
198
|
---
|
|
174
199
|
|
|
175
|
-
##
|
|
200
|
+
## 🚨 Exceptions & Error Handling
|
|
176
201
|
|
|
177
|
-
|
|
202
|
+
The framework provides a set of built-in HTTP exceptions that can be thrown from route handlers or middleware. When an exception is thrown, the framework detects it and sends an appropriate HTTP response with the status code and message you specified in the class.
|
|
178
203
|
|
|
179
204
|
```ts
|
|
180
|
-
import {
|
|
205
|
+
import { NotFoundError, InternalServerError } from "skyguard-js/exceptions";
|
|
181
206
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
207
|
+
const listResources = ["1", "2", "3"];
|
|
208
|
+
|
|
209
|
+
app.get("/resource/{id}", (request: Request) => {
|
|
210
|
+
const resource = request.params["id"];
|
|
211
|
+
|
|
212
|
+
if (!listResources.includes(resource)) {
|
|
213
|
+
throw new NotFoundError("Resource not found");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return Response.json(resource);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
app.get("/divide", (request: Request) => {
|
|
220
|
+
try {
|
|
221
|
+
const { a, b } = request.query;
|
|
222
|
+
const result = Number(a) / Number(b);
|
|
223
|
+
|
|
224
|
+
return Response.json({ result });
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw new InternalServerError(
|
|
227
|
+
"An error occurred while processing your request",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
195
230
|
});
|
|
196
231
|
```
|
|
197
232
|
|
|
198
|
-
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🧱 Sessions
|
|
236
|
+
|
|
237
|
+
To handle sessions, you must use the framework’s built-in middleware. Depending on where you want to store them (in memory, in files, or in a database), you need to use the corresponding storage class.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { sessions } from "skyguard-js/middlewares";
|
|
241
|
+
import { FileSessionStorage } from "skyguard-js";
|
|
242
|
+
|
|
243
|
+
app.middlewares([sessions(FileSessionStorage)]);
|
|
244
|
+
|
|
245
|
+
app.post("/login", (request: Request) => {
|
|
246
|
+
const { username, password } = request.data;
|
|
247
|
+
|
|
248
|
+
if (username === "admin" && password === "secret") {
|
|
249
|
+
request.session.set("user", {
|
|
250
|
+
id: 1,
|
|
251
|
+
username: "admin",
|
|
252
|
+
role: "admin",
|
|
253
|
+
});
|
|
199
254
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
255
|
+
return json({ message: "Logged in" });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
throw new UnauthorizedError("Invalid credentials");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
app.get("/me", (request: Request) => {
|
|
262
|
+
const user = request.session.get("user");
|
|
263
|
+
|
|
264
|
+
if (!user) throw new UnauthorizedError("Not authenticated");
|
|
265
|
+
return json({ user });
|
|
266
|
+
});
|
|
267
|
+
```
|
|
207
268
|
|
|
208
269
|
---
|
|
209
270
|
|
|
210
|
-
##
|
|
271
|
+
## 🛡️ Security
|
|
272
|
+
|
|
273
|
+
The framework includes some password hashing and JWT token generation functions, and also includes JWT authentication middleware.
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { hash, verify, createJWT } from "skyguard-js/security";
|
|
277
|
+
import { authJWT } from "skyguard-js/middlewares";
|
|
211
278
|
|
|
212
|
-
|
|
279
|
+
app.post("/register", async (request: Request) => {
|
|
280
|
+
const { username, password } = request.data;
|
|
281
|
+
const hashedPassword = await hash(password);
|
|
213
282
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
283
|
+
// Save username and hashedPassword to database
|
|
284
|
+
// ...
|
|
285
|
+
|
|
286
|
+
return Response.json({ message: "User registered" });
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
app.post("/login", async (request: Request) => {
|
|
290
|
+
const { username, password } = request.data;
|
|
291
|
+
|
|
292
|
+
// Retrieve user from database by username
|
|
293
|
+
// ...
|
|
294
|
+
|
|
295
|
+
const isValid = await verify(password, user.hashedPassword);
|
|
296
|
+
|
|
297
|
+
if (!isValid) {
|
|
298
|
+
throw new UnauthorizedError("Invalid credentials");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const token = createJWT({ sub: user.id, role: user.role }, "1h");
|
|
302
|
+
|
|
303
|
+
return Response.json({ token });
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 📂 File Uploads
|
|
310
|
+
|
|
311
|
+
To handle file uploads, use the built-in `createUploader` function to create an uploader middleware with the desired storage configuration.
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { createUploader, StorageType } from "skyguard-js";
|
|
315
|
+
|
|
316
|
+
const uploader = createUploader({
|
|
317
|
+
storageType: StorageType.DISK,
|
|
318
|
+
storageOptions: {
|
|
319
|
+
destination: "./uploads",
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
app.post(
|
|
324
|
+
"/upload",
|
|
325
|
+
(request: Request) => {
|
|
326
|
+
return Response.json({
|
|
327
|
+
message: "File uploaded successfully",
|
|
328
|
+
file: request.file,
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
[uploader.single("file")],
|
|
332
|
+
);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 📄 Views & Template Engine
|
|
338
|
+
|
|
339
|
+
To render views, you must first set up the template engine using the `engineTemplates` method of the `app`, set the view path with the `views` method of the `app`, and then you can use the `render` method within your handlers to render the views with the data you want to pass.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
import { engine } from "express-handlebars";
|
|
343
|
+
import ejs from "ejs";
|
|
344
|
+
import { join } from "node:path";
|
|
345
|
+
|
|
346
|
+
app.views(__dirname, "views");
|
|
347
|
+
|
|
348
|
+
// Config for Express Handlebars
|
|
349
|
+
app.engineTemplates(
|
|
350
|
+
"hbs",
|
|
351
|
+
engine({
|
|
352
|
+
extname: "hbs",
|
|
353
|
+
layoutsDir: join(__dirname, "views"),
|
|
354
|
+
defaultLayout: "main",
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Config for EJS
|
|
359
|
+
app.engineTemplates("ejs", (templatePath, data) => {
|
|
360
|
+
return ejs.renderFile(templatePath, data);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
app.get("/home", () => {
|
|
364
|
+
return render("index", {
|
|
365
|
+
title: "Home Page",
|
|
366
|
+
message: "Welcome to the home page!",
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Currently, it works with third-party template engines such as **Express Handlebars**, **Pug**, and **EJS**, but the idea is to implement its own template engine in the future.
|
|
218
372
|
|
|
219
373
|
---
|
|
220
374
|
|
|
221
375
|
## 🔮 Roadmap (Tentative)
|
|
222
376
|
|
|
223
377
|
- Middleware system (✅)
|
|
224
|
-
- Template
|
|
378
|
+
- Template engines supported (✅)
|
|
225
379
|
- Request / Response abstraction (✅)
|
|
226
380
|
- Data validation (✅)
|
|
227
381
|
- Error handling improvements (✅)
|
|
228
|
-
- Sessions & cookies (
|
|
229
|
-
-
|
|
382
|
+
- Sessions & cookies (✅)
|
|
383
|
+
- Passoword hashing & JWT tokens (✅)
|
|
384
|
+
- File uploads (✅)
|
|
230
385
|
- Database & ORM integration
|
|
386
|
+
- Authentication & authorization
|
|
387
|
+
- WebSockets
|
|
231
388
|
|
|
232
389
|
---
|
|
233
390
|
|
package/dist/app.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RouterGroup } from "./routing";
|
|
2
|
-
import { type View } from "./views";
|
|
3
2
|
import type { Middleware, RouteHandler } from "./types";
|
|
3
|
+
import { type TemplateEngineFunction } from "./views/engineTemplate";
|
|
4
4
|
/**
|
|
5
5
|
* The `App` class acts as the **execution kernel** and **lifecycle orchestrator**
|
|
6
6
|
* of the framework.
|
|
@@ -22,14 +22,14 @@ import type { Middleware, RouteHandler } from "./types";
|
|
|
22
22
|
export declare class App {
|
|
23
23
|
/** Main routing system */
|
|
24
24
|
private router;
|
|
25
|
-
/**
|
|
26
|
-
* View engine used to render templates.
|
|
27
|
-
*
|
|
28
|
-
* Typically consumed inside controllers to generate HTML responses.
|
|
29
|
-
*/
|
|
30
|
-
view: View;
|
|
31
25
|
/** Static file handler (optional) */
|
|
32
26
|
private staticFileHandler;
|
|
27
|
+
/** Logger instance for request logging */
|
|
28
|
+
private logger;
|
|
29
|
+
/** Timestamp marking the start of request processing (for logging) */
|
|
30
|
+
private startTime;
|
|
31
|
+
/** View engine for rendering templates (optional) */
|
|
32
|
+
private viewEngine;
|
|
33
33
|
/**
|
|
34
34
|
* Bootstraps and configures the application.
|
|
35
35
|
*
|
|
@@ -65,6 +65,42 @@ export declare class App {
|
|
|
65
65
|
* // /public/css/style.css → /css/style.css
|
|
66
66
|
*/
|
|
67
67
|
staticFiles(publicPath: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Configures the views directory for template rendering.
|
|
70
|
+
*
|
|
71
|
+
* @param pathSegments Path segments leading to the views directory
|
|
72
|
+
* @example
|
|
73
|
+
* app.views(__dirname, "views");
|
|
74
|
+
*/
|
|
75
|
+
views(...pathSegments: string[]): void;
|
|
76
|
+
/**
|
|
77
|
+
* Configures the template engine for rendering views.
|
|
78
|
+
*
|
|
79
|
+
* @param extension - File extension associated with the template engine (e.g. "hbs", "ejs")
|
|
80
|
+
* @param engine - Function that renders a template given its path and data
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* app.engineTemplates("hbs", (templatePath, data) => {
|
|
84
|
+
* const content = fs.readFileSync(templatePath, "utf-8");
|
|
85
|
+
* return content.replace(/{{\s*(\w+)\s*}}/g, (_, key) => {
|
|
86
|
+
* return data[key] ?? "";
|
|
87
|
+
* });
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* // With express-handlebars
|
|
91
|
+
* app.engineTemplates(
|
|
92
|
+
* "hbs",
|
|
93
|
+
* engine({
|
|
94
|
+
* extname: "hbs",
|
|
95
|
+
* layoutsDir: join(__dirname, "views"),
|
|
96
|
+
* defaultLayout: "main",
|
|
97
|
+
* }),
|
|
98
|
+
* );
|
|
99
|
+
*
|
|
100
|
+
* The `extension` parameter allows the framework to automatically select the correct
|
|
101
|
+
* template engine based on the file extension of the view being rendered.
|
|
102
|
+
*/
|
|
103
|
+
engineTemplates(extension: string, engine: TemplateEngineFunction): void;
|
|
68
104
|
/**
|
|
69
105
|
* Starts the HTTP server on the given port.
|
|
70
106
|
*
|
package/dist/app.js
CHANGED
|
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createApp = exports.App = void 0;
|
|
4
4
|
const routing_1 = require("./routing");
|
|
5
5
|
const http_1 = require("./http");
|
|
6
|
-
const
|
|
7
|
-
const views_1 = require("./views");
|
|
6
|
+
const validationException_1 = require("./exceptions/validationException");
|
|
8
7
|
const node_path_1 = require("node:path");
|
|
9
|
-
const app_1 = require("./helpers/app");
|
|
10
8
|
const fileStaticHandler_1 = require("./static/fileStaticHandler");
|
|
11
9
|
const node_http_1 = require("node:http");
|
|
12
10
|
const httpExceptions_1 = require("./exceptions/httpExceptions");
|
|
11
|
+
const engineTemplate_1 = require("./views/engineTemplate");
|
|
12
|
+
const container_1 = require("./container/container");
|
|
13
13
|
/**
|
|
14
14
|
* The `App` class acts as the **execution kernel** and **lifecycle orchestrator**
|
|
15
15
|
* of the framework.
|
|
@@ -31,14 +31,14 @@ const httpExceptions_1 = require("./exceptions/httpExceptions");
|
|
|
31
31
|
class App {
|
|
32
32
|
/** Main routing system */
|
|
33
33
|
router;
|
|
34
|
-
/**
|
|
35
|
-
* View engine used to render templates.
|
|
36
|
-
*
|
|
37
|
-
* Typically consumed inside controllers to generate HTML responses.
|
|
38
|
-
*/
|
|
39
|
-
view;
|
|
40
34
|
/** Static file handler (optional) */
|
|
41
35
|
staticFileHandler = null;
|
|
36
|
+
/** Logger instance for request logging */
|
|
37
|
+
logger;
|
|
38
|
+
/** Timestamp marking the start of request processing (for logging) */
|
|
39
|
+
startTime;
|
|
40
|
+
/** View engine for rendering templates (optional) */
|
|
41
|
+
viewEngine;
|
|
42
42
|
/**
|
|
43
43
|
* Bootstraps and configures the application.
|
|
44
44
|
*
|
|
@@ -49,9 +49,10 @@ class App {
|
|
|
49
49
|
* @returns The singleton `App` instance
|
|
50
50
|
*/
|
|
51
51
|
static bootstrap() {
|
|
52
|
-
const app =
|
|
52
|
+
const app = container_1.Container.singleton(App);
|
|
53
53
|
app.router = new routing_1.Router();
|
|
54
|
-
app.
|
|
54
|
+
app.logger = new http_1.Logger();
|
|
55
|
+
app.viewEngine = container_1.Container.singleton(engineTemplate_1.ViewEngine);
|
|
55
56
|
return app;
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
@@ -71,8 +72,8 @@ class App {
|
|
|
71
72
|
async handle(adapter) {
|
|
72
73
|
try {
|
|
73
74
|
const request = await adapter.getRequest();
|
|
74
|
-
if (this.staticFileHandler && request.
|
|
75
|
-
const staticResponse = await this.staticFileHandler.tryServeFile(request.
|
|
75
|
+
if (this.staticFileHandler && request.method === http_1.HttpMethods.get) {
|
|
76
|
+
const staticResponse = await this.staticFileHandler.tryServeFile(request.url);
|
|
76
77
|
if (staticResponse) {
|
|
77
78
|
adapter.sendResponse(staticResponse);
|
|
78
79
|
return;
|
|
@@ -97,6 +98,46 @@ class App {
|
|
|
97
98
|
staticFiles(publicPath) {
|
|
98
99
|
this.staticFileHandler = new fileStaticHandler_1.StaticFileHandler(publicPath);
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Configures the views directory for template rendering.
|
|
103
|
+
*
|
|
104
|
+
* @param pathSegments Path segments leading to the views directory
|
|
105
|
+
* @example
|
|
106
|
+
* app.views(__dirname, "views");
|
|
107
|
+
*/
|
|
108
|
+
views(...pathSegments) {
|
|
109
|
+
this.viewEngine.setViewsPath((0, node_path_1.join)(...pathSegments));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Configures the template engine for rendering views.
|
|
113
|
+
*
|
|
114
|
+
* @param extension - File extension associated with the template engine (e.g. "hbs", "ejs")
|
|
115
|
+
* @param engine - Function that renders a template given its path and data
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* app.engineTemplates("hbs", (templatePath, data) => {
|
|
119
|
+
* const content = fs.readFileSync(templatePath, "utf-8");
|
|
120
|
+
* return content.replace(/{{\s*(\w+)\s*}}/g, (_, key) => {
|
|
121
|
+
* return data[key] ?? "";
|
|
122
|
+
* });
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* // With express-handlebars
|
|
126
|
+
* app.engineTemplates(
|
|
127
|
+
* "hbs",
|
|
128
|
+
* engine({
|
|
129
|
+
* extname: "hbs",
|
|
130
|
+
* layoutsDir: join(__dirname, "views"),
|
|
131
|
+
* defaultLayout: "main",
|
|
132
|
+
* }),
|
|
133
|
+
* );
|
|
134
|
+
*
|
|
135
|
+
* The `extension` parameter allows the framework to automatically select the correct
|
|
136
|
+
* template engine based on the file extension of the view being rendered.
|
|
137
|
+
*/
|
|
138
|
+
engineTemplates(extension, engine) {
|
|
139
|
+
this.viewEngine.setEngine(extension, engine);
|
|
140
|
+
}
|
|
100
141
|
/**
|
|
101
142
|
* Starts the HTTP server on the given port.
|
|
102
143
|
*
|
|
@@ -107,8 +148,10 @@ class App {
|
|
|
107
148
|
*/
|
|
108
149
|
run(port, callback, hostname = "127.0.0.1") {
|
|
109
150
|
(0, node_http_1.createServer)((req, res) => {
|
|
151
|
+
this.startTime = process.hrtime.bigint();
|
|
110
152
|
const adapter = new http_1.NodeHttpAdapter(req, res);
|
|
111
153
|
void this.handle(adapter);
|
|
154
|
+
this.logger.log(req, res, this.startTime);
|
|
112
155
|
}).listen(port, hostname, () => {
|
|
113
156
|
callback();
|
|
114
157
|
});
|
|
@@ -172,20 +215,19 @@ class App {
|
|
|
172
215
|
*/
|
|
173
216
|
handleError(error, adapter) {
|
|
174
217
|
if (error instanceof httpExceptions_1.HttpException) {
|
|
175
|
-
adapter.sendResponse(http_1.Response.json(error.toJSON()).
|
|
218
|
+
adapter.sendResponse(http_1.Response.json(error.toJSON()).setStatusCode(error.statusCode));
|
|
176
219
|
return;
|
|
177
220
|
}
|
|
178
|
-
if (error instanceof
|
|
221
|
+
if (error instanceof validationException_1.ValidationException) {
|
|
179
222
|
adapter.sendResponse(http_1.Response.json({
|
|
180
|
-
success: false,
|
|
181
223
|
errors: error.getErrorsByField(),
|
|
182
|
-
}).
|
|
224
|
+
}).setStatusCode(400));
|
|
183
225
|
return;
|
|
184
226
|
}
|
|
185
227
|
adapter.sendResponse(http_1.Response.json({
|
|
186
228
|
statusCode: 500,
|
|
187
229
|
message: "Internal Server Error",
|
|
188
|
-
}).
|
|
230
|
+
}).setStatusCode(500));
|
|
189
231
|
console.error(error);
|
|
190
232
|
}
|
|
191
233
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
type ScryptOptions = {
|
|
2
|
+
cost: number;
|
|
3
|
+
blockSize: number;
|
|
4
|
+
parallelization: number;
|
|
5
|
+
maxmem?: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Hashes a plaintext password using scrypt with an unique random salt.
|
|
9
|
+
*
|
|
10
|
+
* Output format:
|
|
11
|
+
* `scrypt$<cost>$<blockSize>$<parallelization>$<saltHex>$<hashHex>`
|
|
12
|
+
*
|
|
13
|
+
* @param password - Plaintext password.
|
|
14
|
+
* @param saltLength - Salt length in bytes. Default `16` (recommended minimum).
|
|
15
|
+
* @param params - Scrypt work-factor params. Defaults to `DEFAULT_PARAMS`.
|
|
16
|
+
* @param pepper - Optional server secret mixed into the password (e.g., from env var).
|
|
17
|
+
* @returns A compact encoded hash string containing algorithm parameters + salt + derived key.
|
|
18
|
+
*/
|
|
19
|
+
export declare const hash: (password: string, saltLength?: number, params?: ScryptOptions, pepper?: string) => Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Verifies a plaintext password against a stored scrypt hash string.
|
|
22
|
+
*
|
|
23
|
+
* Safe failure behavior:
|
|
24
|
+
* - Returns `false` for any parsing error, invalid encoding, mismatched lengths,
|
|
25
|
+
* or scrypt errors. This avoids leaking details about why verification failed.
|
|
26
|
+
*
|
|
27
|
+
* @param password - Plaintext password to verify.
|
|
28
|
+
* @param storedHash - Stored hash string in the compact format.
|
|
29
|
+
* @param pepper - Optional server secret; must match the one used when hashing.
|
|
30
|
+
* @returns `true` if the password matches, otherwise `false`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const verify: (password: string, storedHash: string, pepper?: string) => Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Indicates whether a stored hash should be regenerated using the current parameters.
|
|
35
|
+
*
|
|
36
|
+
* Use this after successful login:
|
|
37
|
+
* - If `needsRehash(...) === true`, compute a new hash using `hash(...)` with
|
|
38
|
+
* the latest parameters and store it back to the DB.
|
|
39
|
+
*
|
|
40
|
+
* This enables gradual upgrades of the work factor without forcing password resets.
|
|
41
|
+
*
|
|
42
|
+
* @param storedHash - Stored hash string in compact format.
|
|
43
|
+
* @param params - Desired/current scrypt params. Defaults to `DEFAULT_PARAMS`.
|
|
44
|
+
* @returns `true` if the hash is missing/invalid or was produced with different parameters.
|
|
45
|
+
*/
|
|
46
|
+
export declare const needsRehash: (storedHash: string, params?: ScryptOptions) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Hashes multiple passwords using controlled concurrency.
|
|
49
|
+
*
|
|
50
|
+
* Why limit concurrency?
|
|
51
|
+
* scrypt is intentionally CPU + memory intensive. Running too many in parallel can:
|
|
52
|
+
* - saturate the libuv threadpool,
|
|
53
|
+
* - exceed memory limits (especially in containers),
|
|
54
|
+
* - increase latency for the rest of the application.
|
|
55
|
+
*
|
|
56
|
+
* The `concurrency` option caps the number of simultaneous scrypt operations.
|
|
57
|
+
*
|
|
58
|
+
* @param passwords - List of plaintext passwords.
|
|
59
|
+
* @param options - Optional hashing controls:
|
|
60
|
+
* - saltLength: salt bytes (default 16)
|
|
61
|
+
* - params: scrypt params (default DEFAULT_PARAMS)
|
|
62
|
+
* - pepper: optional server secret
|
|
63
|
+
* - concurrency: max simultaneous operations (default 4)
|
|
64
|
+
* @returns Array of compact hash strings in the same order as input.
|
|
65
|
+
*/
|
|
66
|
+
export declare const hashBatch: (passwords: string[], options?: {
|
|
67
|
+
saltLength?: number;
|
|
68
|
+
params?: ScryptOptions;
|
|
69
|
+
pepper?: string;
|
|
70
|
+
concurrency?: number;
|
|
71
|
+
}) => Promise<string[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Verifies multiple password/hash pairs using controlled concurrency.
|
|
74
|
+
*
|
|
75
|
+
* This is useful for bulk checks or migrations. Concurrency is typically higher
|
|
76
|
+
* than hashing, but still should be bounded to avoid saturating the threadpool.
|
|
77
|
+
*
|
|
78
|
+
* @param credentials - Array of `{ password, hash }` pairs.
|
|
79
|
+
* @param options - Optional verification controls:
|
|
80
|
+
* - pepper: optional server secret
|
|
81
|
+
* - concurrency: max simultaneous operations (default 8)
|
|
82
|
+
* @returns Array of booleans in the same order as input.
|
|
83
|
+
*/
|
|
84
|
+
export declare const verifyBatch: (credentials: Array<{
|
|
85
|
+
password: string;
|
|
86
|
+
hash: string;
|
|
87
|
+
}>, options?: {
|
|
88
|
+
pepper?: string;
|
|
89
|
+
concurrency?: number;
|
|
90
|
+
}) => Promise<boolean[]>;
|
|
91
|
+
export {};
|