cpeak 2.4.3 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -39
- package/dist/index.d.ts +75 -16
- package/dist/index.js +515 -169
- package/dist/index.js.map +1 -1
- package/lib/index.ts +262 -217
- package/lib/types.ts +12 -10
- package/lib/utils/auth.ts +170 -0
- package/lib/utils/cookieParser.ts +189 -0
- package/lib/utils/index.ts +16 -1
- package/lib/utils/parseJSON.ts +77 -24
- package/lib/utils/serveStatic.ts +16 -5
- package/lib/utils/swagger.ts +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,9 @@ This is an educational project that was started as part of the [Understanding No
|
|
|
34
34
|
- [serveStatic](#servestatic)
|
|
35
35
|
- [parseJSON](#parsejson)
|
|
36
36
|
- [render](#render)
|
|
37
|
+
- [cookieParser](#cookieparser)
|
|
38
|
+
- [swagger](#swagger)
|
|
39
|
+
- [auth](#auth)
|
|
37
40
|
- [Complete Example](#complete-example)
|
|
38
41
|
- [Versioning Notice](#versioning-notice)
|
|
39
42
|
|
|
@@ -131,7 +134,7 @@ const requireAuth = (req, res, next, handleErr) => {
|
|
|
131
134
|
return handleErr({ status: 401, message: "Unauthorized" });
|
|
132
135
|
};
|
|
133
136
|
|
|
134
|
-
server.route("get", "/profile", requireAuth, (req, res
|
|
137
|
+
server.route("get", "/profile", requireAuth, (req, res) => {
|
|
135
138
|
console.log(req.test); // this is a test value
|
|
136
139
|
});
|
|
137
140
|
```
|
|
@@ -145,7 +148,7 @@ server.route(
|
|
|
145
148
|
requireAuth,
|
|
146
149
|
anotherFunction,
|
|
147
150
|
oneMore,
|
|
148
|
-
(req, res
|
|
151
|
+
(req, res) => {
|
|
149
152
|
// your logic
|
|
150
153
|
}
|
|
151
154
|
);
|
|
@@ -165,16 +168,15 @@ First add the HTTP method name you want to handle, then the path, and finally, t
|
|
|
165
168
|
|
|
166
169
|
### URL Variables & Parameters
|
|
167
170
|
|
|
168
|
-
|
|
169
|
-
We can also do custom path management, and we call them `vars` (short for URL variables).
|
|
171
|
+
To be more consistent with the broader Node.js community and frameworks, we call the HTTP URL parameters (query strings) '**query**', and the path variables (route parameters) '**params**'.
|
|
170
172
|
|
|
171
173
|
Here’s how we can read both:
|
|
172
174
|
|
|
173
175
|
```javascript
|
|
174
176
|
// Imagine request URL is example.com/test/my-title/more-text?filter=newest
|
|
175
177
|
server.route("patch", "/test/:title/more-text", (req, res) => {
|
|
176
|
-
const title = req.
|
|
177
|
-
const filter = req.
|
|
178
|
+
const title = req.params.title;
|
|
179
|
+
const filter = req.query.filter;
|
|
178
180
|
|
|
179
181
|
console.log(title); // my-title
|
|
180
182
|
console.log(filter); // newest
|
|
@@ -206,11 +208,23 @@ res.redirect("https://whatever.com");
|
|
|
206
208
|
|
|
207
209
|
### Error Handling
|
|
208
210
|
|
|
209
|
-
If anywhere in your route functions or route middleware functions you want to return an error,
|
|
211
|
+
If anywhere in your route functions or route middleware functions you want to return an error, you can just throw the error and let the automatic error handler catch it:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
server.route("get", "/api/document/:title", (req, res) => {
|
|
215
|
+
const title = req.params.title;
|
|
216
|
+
|
|
217
|
+
if (title.length > 500) throw { status: 400, message: "Title too long." };
|
|
218
|
+
|
|
219
|
+
// The rest of your logic...
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
You can also make use of the `handleErr` callback function like this:
|
|
210
224
|
|
|
211
225
|
```javascript
|
|
212
226
|
server.route("get", "/api/document/:title", (req, res, handleErr) => {
|
|
213
|
-
const title = req.
|
|
227
|
+
const title = req.params.title;
|
|
214
228
|
|
|
215
229
|
if (title.length > 500)
|
|
216
230
|
return handleErr({ status: 400, message: "Title too long." });
|
|
@@ -219,7 +233,7 @@ server.route("get", "/api/document/:title", (req, res, handleErr) => {
|
|
|
219
233
|
});
|
|
220
234
|
```
|
|
221
235
|
|
|
222
|
-
|
|
236
|
+
**Make sure** to call the `server.handleErr` and pass a function like this to have the automatic error handler work properly:
|
|
223
237
|
|
|
224
238
|
```javascript
|
|
225
239
|
server.handleErr((error, req, res) => {
|
|
@@ -229,13 +243,13 @@ server.handleErr((error, req, res) => {
|
|
|
229
243
|
// Log the unexpected errors somewhere so you can keep track of them...
|
|
230
244
|
console.error(error);
|
|
231
245
|
res.status(500).json({
|
|
232
|
-
error: "Sorry, something unexpected happened on our side."
|
|
246
|
+
error: "Sorry, something unexpected happened on our side."
|
|
233
247
|
});
|
|
234
248
|
}
|
|
235
249
|
});
|
|
236
250
|
```
|
|
237
251
|
|
|
238
|
-
|
|
252
|
+
_The error object is the object that you threw or passed to the `handleErr` function earlier in your routes._
|
|
239
253
|
|
|
240
254
|
### Listening
|
|
241
255
|
|
|
@@ -256,6 +270,9 @@ The list of utility functions as of now:
|
|
|
256
270
|
- serveStatic
|
|
257
271
|
- parseJSON
|
|
258
272
|
- render
|
|
273
|
+
- cookieParser
|
|
274
|
+
- swagger
|
|
275
|
+
- auth
|
|
259
276
|
|
|
260
277
|
Including any one of them is done like this:
|
|
261
278
|
|
|
@@ -270,7 +287,7 @@ With this middleware function, you can automatically set a folder in your projec
|
|
|
270
287
|
```javascript
|
|
271
288
|
server.beforeEach(
|
|
272
289
|
serveStatic("./public", {
|
|
273
|
-
mp3: "audio/mpeg"
|
|
290
|
+
mp3: "audio/mpeg"
|
|
274
291
|
})
|
|
275
292
|
);
|
|
276
293
|
```
|
|
@@ -285,7 +302,11 @@ If you have file types in your public folder that are not one of the following,
|
|
|
285
302
|
jpeg: "image/jpeg",
|
|
286
303
|
png: "image/png",
|
|
287
304
|
svg: "image/svg+xml",
|
|
305
|
+
gif: "image/gif",
|
|
306
|
+
ico: "image/x-icon",
|
|
288
307
|
txt: "text/plain",
|
|
308
|
+
json: "application/json",
|
|
309
|
+
webmanifest: "application/manifest+json",
|
|
289
310
|
eot: "application/vnd.ms-fontobject",
|
|
290
311
|
otf: "font/otf",
|
|
291
312
|
ttf: "font/ttf",
|
|
@@ -293,12 +314,24 @@ If you have file types in your public folder that are not one of the following,
|
|
|
293
314
|
woff2: "font/woff2"
|
|
294
315
|
```
|
|
295
316
|
|
|
317
|
+
You can also serve your static files under a URL prefix by passing a third argument with a `prefix` option. This is useful when you want all static assets to live under a specific path like `/static`:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
server.beforeEach(
|
|
321
|
+
serveStatic("./public", null, { prefix: "/static" })
|
|
322
|
+
);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
With this setup, a file at `./public/app.js` would be served at `/static/app.js` instead of `/app.js`. Pass `null` as the second argument if you don’t need any custom MIME types.
|
|
326
|
+
|
|
296
327
|
#### parseJSON
|
|
297
328
|
|
|
298
329
|
With this middleware function, you can easily read and send JSON in HTTP message bodies in route and middleware functions. Fire it up like this:
|
|
299
330
|
|
|
300
331
|
```javascript
|
|
301
|
-
|
|
332
|
+
// You can pass an optional limit option to indicate the maximum
|
|
333
|
+
// JSON body size that your server will accept.
|
|
334
|
+
server.beforeEach(parseJSON({ limit: 1024 * 1024 })); // default value is 1024 * 1024 (1MB)
|
|
302
335
|
```
|
|
303
336
|
|
|
304
337
|
Read and send JSON from HTTP messages like this:
|
|
@@ -333,7 +366,7 @@ server.route("get", "/", (req, res, next) => {
|
|
|
333
366
|
"./public/index.html",
|
|
334
367
|
{
|
|
335
368
|
title: "Page title",
|
|
336
|
-
name: "Allan"
|
|
369
|
+
name: "Allan"
|
|
337
370
|
},
|
|
338
371
|
"text/html"
|
|
339
372
|
);
|
|
@@ -353,25 +386,213 @@ You can then inject the variables into your file in {{ variable_name }} like thi
|
|
|
353
386
|
</html>
|
|
354
387
|
```
|
|
355
388
|
|
|
389
|
+
#### cookieParser
|
|
390
|
+
|
|
391
|
+
With this middleware function, you can easily read and set cookies in your route and middleware functions. Fire it up like this:
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
server.beforeEach(cookieParser());
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
If you need to use signed cookies, pass a secret:
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
server.beforeEach(cookieParser({ secret: "your-secret-key" }));
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Signed cookies use HMAC to verify integrity. The original value stays readable by the client, but any tampering with it will be detected on the server side. This makes them a solid choice for session identifiers or user IDs where you want to prevent impersonation without hiding the value itself.
|
|
404
|
+
|
|
405
|
+
Read incoming cookies like this:
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
server.route("get", "/dashboard", (req, res) => {
|
|
409
|
+
// Regular cookies
|
|
410
|
+
const theme = req.cookies.theme;
|
|
411
|
+
|
|
412
|
+
// Signed cookies — returns false if the signature is invalid or the value was tampered with
|
|
413
|
+
const userId = req.signedCookies.userId;
|
|
414
|
+
|
|
415
|
+
res.status(200).json({ theme, userId });
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Set cookies on the response like this:
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
server.route("post", "/login", (req, res) => {
|
|
423
|
+
// A plain cookie
|
|
424
|
+
res.cookie("theme", "dark", { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000 });
|
|
425
|
+
|
|
426
|
+
// A signed cookie
|
|
427
|
+
res.cookie("userId", "abc123", { signed: true, httpOnly: true, secure: true });
|
|
428
|
+
|
|
429
|
+
res.status(200).json({ message: "Logged in" });
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Clear a cookie like this:
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
res.clearCookie("userId");
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The full list of cookie options you can pass as the third argument to `res.cookie()`:
|
|
440
|
+
|
|
441
|
+
- `signed` — sign the cookie value with HMAC using the secret you provided to `cookieParser`
|
|
442
|
+
- `httpOnly` — prevents client-side JavaScript from accessing the cookie
|
|
443
|
+
- `secure` — instructs the browser to send the cookie only over HTTPS
|
|
444
|
+
- `sameSite` — controls cross-site cookie behavior; accepts `"strict"`, `"lax"`, or `"none"`
|
|
445
|
+
- `maxAge` — cookie lifetime in milliseconds
|
|
446
|
+
- `expires` — a specific expiration `Date` for the cookie
|
|
447
|
+
- `path` — path the cookie is valid for (defaults to `"/"`)
|
|
448
|
+
- `domain` — domain the cookie is valid for
|
|
449
|
+
|
|
450
|
+
#### swagger
|
|
451
|
+
|
|
452
|
+
With this middleware function, you can serve an interactive Swagger UI for your API documentation. It works alongside the `serveStatic` utility and two npm packages: `swagger-ui-dist` (the Swagger UI static assets) and `yamljs` (to load your YAML spec file).
|
|
453
|
+
|
|
454
|
+
Start by installing the dependencies:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
npm install swagger-ui-dist yamljs
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Then fire it up like this:
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
import cpeak, { swagger, serveStatic } from "cpeak";
|
|
464
|
+
import YAML from "yamljs";
|
|
465
|
+
import swaggerUiDist from "swagger-ui-dist";
|
|
466
|
+
import path from "node:path";
|
|
467
|
+
|
|
468
|
+
const server = cpeak();
|
|
469
|
+
|
|
470
|
+
const swaggerDocument = YAML.load(
|
|
471
|
+
path.join(path.resolve(), "./src/swagger.yml")
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
server.beforeEach(swagger(swaggerDocument));
|
|
475
|
+
server.beforeEach(
|
|
476
|
+
serveStatic(swaggerUiDist.getAbsoluteFSPath(), undefined, {
|
|
477
|
+
prefix: "/api-docs",
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Once set up, your Swagger UI will be available at `/api-docs`. The `swagger` middleware handles serving your spec at `/api-docs/spec.json` and wiring up the Swagger UI initializer, while `serveStatic` serves all the Swagger UI static assets under the same prefix.
|
|
483
|
+
|
|
484
|
+
If you want to serve the docs under a different path, pass it as the second argument to `swagger` and match the prefix in `serveStatic`:
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
server.beforeEach(swagger(swaggerDocument, "/docs"));
|
|
488
|
+
server.beforeEach(
|
|
489
|
+
serveStatic(swaggerUiDist.getAbsoluteFSPath(), undefined, {
|
|
490
|
+
prefix: "/docs",
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### auth
|
|
496
|
+
|
|
497
|
+
With this middleware you can add a full-fledged authentication system to your application with emails, username and password authentication, with features such as Forgot Password, Update Password and so forth. We have no external dependencies, with timing-safe comparisons throughout. It attaches helper methods directly to `req` so your route handlers stay clean.
|
|
498
|
+
|
|
499
|
+
Fire it up like this:
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
import cpeak, { parseJSON, cookieParser, auth } from "cpeak";
|
|
503
|
+
|
|
504
|
+
const app = cpeak();
|
|
505
|
+
|
|
506
|
+
app.beforeEach(parseJSON());
|
|
507
|
+
app.beforeEach(cookieParser());
|
|
508
|
+
|
|
509
|
+
app.beforeEach(
|
|
510
|
+
auth({
|
|
511
|
+
// Required
|
|
512
|
+
secret: "your-secret-min-32-chars-long!!!", // used to sign token IDs with HMAC
|
|
513
|
+
saveToken: async (tokenId, userId, expiresAt) => { /* store in your DB */ },
|
|
514
|
+
findToken: async (tokenId) => { /* return { userId, expiresAt } or null */ },
|
|
515
|
+
|
|
516
|
+
// Enables req.logout()
|
|
517
|
+
revokeToken: async (tokenId) => { /* delete from your DB */ },
|
|
518
|
+
|
|
519
|
+
// Optional PBKDF2 tuning (defaults shown):
|
|
520
|
+
iterations: 210_000, // higher = slower brute-force
|
|
521
|
+
keylen: 64, // derived key length in bytes
|
|
522
|
+
digest: "sha512",
|
|
523
|
+
saltSize: 32,
|
|
524
|
+
|
|
525
|
+
// Optional token signing tuning (defaults shown):
|
|
526
|
+
hmacAlgorithm: "sha256",
|
|
527
|
+
tokenIdSize: 20,
|
|
528
|
+
tokenExpiry: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Once set up, the following methods are available on `req` inside your routes and middleware:
|
|
534
|
+
|
|
535
|
+
| Method | Description |
|
|
536
|
+
|--------|-------------|
|
|
537
|
+
| `req.hashPassword({ password })` | Hashes a password with PBKDF2. Store the result; never store plaintext. |
|
|
538
|
+
| `req.login({ password, hashedPassword, userId })` | Verifies the password and if correct, creates a signed token. Returns the token string to send to the client, or `null` on wrong password. |
|
|
539
|
+
| `req.verifyToken(token)` | Validates a token's HMAC signature and expiry. Returns `{ userId }` or `null`. |
|
|
540
|
+
| `req.logout(token)` | Revokes the token via your `revokeToken` callback. Only available when `revokeToken` is provided. |
|
|
541
|
+
|
|
542
|
+
Here are the two most common middleware patterns you'll want to set up:
|
|
543
|
+
|
|
544
|
+
```javascript
|
|
545
|
+
// Throws 401 if the request has no valid token. Use on protected routes.
|
|
546
|
+
const requireAuth = async (req, res, next) => {
|
|
547
|
+
const token = req.headers["authorization"];
|
|
548
|
+
if (!token) throw { status: 401, message: "Unauthorized." };
|
|
549
|
+
|
|
550
|
+
const result = await req.verifyToken(token);
|
|
551
|
+
if (!result) throw { status: 401, message: "Unauthorized." };
|
|
552
|
+
|
|
553
|
+
req.user = { id: result.userId };
|
|
554
|
+
next();
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// Silently sets req.user when a valid token is present, but lets the request through either way.
|
|
558
|
+
// Useful for routes accessible by both authenticated and unauthenticated users.
|
|
559
|
+
const optionalAuth = async (req, _res, next) => {
|
|
560
|
+
const token = req.headers["authorization"];
|
|
561
|
+
if (token) {
|
|
562
|
+
const result = await req.verifyToken(token);
|
|
563
|
+
if (result) req.user = { id: result.userId };
|
|
564
|
+
}
|
|
565
|
+
next();
|
|
566
|
+
};
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
For complete working examples, see:
|
|
570
|
+
|
|
571
|
+
- [`examples/auth-localstorage.js`](examples/auth-localstorage.js) — token sent via the `Authorization` header (suited for SPAs and mobile clients)
|
|
572
|
+
- [`examples/auth-cookies.js`](examples/auth-cookies.js) — token stored in an `httpOnly` cookie (protects against XSS)
|
|
573
|
+
|
|
356
574
|
## Complete Example
|
|
357
575
|
|
|
358
|
-
Here you can see all the features that Cpeak offers, in one small piece of code:
|
|
576
|
+
Here you can see all the features that Cpeak offers (excluding the authentication features), in one small piece of code:
|
|
359
577
|
|
|
360
578
|
```javascript
|
|
361
|
-
import cpeak, { serveStatic, parseJSON, render } from "cpeak";
|
|
579
|
+
import cpeak, { serveStatic, parseJSON, render, cookieParser } from "cpeak";
|
|
362
580
|
|
|
363
581
|
const server = cpeak();
|
|
364
582
|
|
|
365
583
|
server.beforeEach(
|
|
366
584
|
serveStatic("./public", {
|
|
367
|
-
mp3: "audio/mpeg"
|
|
585
|
+
mp3: "audio/mpeg"
|
|
368
586
|
})
|
|
369
587
|
);
|
|
370
588
|
|
|
371
589
|
server.beforeEach(render());
|
|
372
590
|
|
|
373
591
|
// For parsing JSON bodies
|
|
374
|
-
server.beforeEach(parseJSON);
|
|
592
|
+
server.beforeEach(parseJSON());
|
|
593
|
+
|
|
594
|
+
// For reading and setting cookies
|
|
595
|
+
server.beforeEach(cookieParser({ secret: "your-secret-key" }));
|
|
375
596
|
|
|
376
597
|
// Adding custom middleware functions
|
|
377
598
|
server.beforeEach((req, res, next) => {
|
|
@@ -383,7 +604,7 @@ server.beforeEach((req, res, next) => {
|
|
|
383
604
|
const testRouteMiddleware = (req, res, next, handleErr) => {
|
|
384
605
|
req.whatever = "some calculated value maybe";
|
|
385
606
|
|
|
386
|
-
if (req.
|
|
607
|
+
if (req.params.test !== "something special") {
|
|
387
608
|
return handleErr({ status: 400, message: "an error message" });
|
|
388
609
|
}
|
|
389
610
|
|
|
@@ -396,7 +617,7 @@ server.route("get", "/", (req, res, next) => {
|
|
|
396
617
|
"<path-to-file-relative-to-cwd>",
|
|
397
618
|
{
|
|
398
619
|
test: "some testing value",
|
|
399
|
-
number: "2343242"
|
|
620
|
+
number: "2343242"
|
|
400
621
|
},
|
|
401
622
|
"<mime-type>"
|
|
402
623
|
);
|
|
@@ -406,28 +627,33 @@ server.route("get", "/old-url", testRouteMiddleware, (req, res, next) => {
|
|
|
406
627
|
return res.redirect("/new-url");
|
|
407
628
|
});
|
|
408
629
|
|
|
409
|
-
server.route(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
testRouteMiddleware,
|
|
413
|
-
(req, res, handleErr) => {
|
|
414
|
-
// Reading URL variables
|
|
415
|
-
const title = req.vars.title;
|
|
630
|
+
server.route("get", "/api/document/:title", testRouteMiddleware, (req, res) => {
|
|
631
|
+
// Reading URL variables (route parameters)
|
|
632
|
+
const title = req.params.title;
|
|
416
633
|
|
|
417
|
-
|
|
418
|
-
|
|
634
|
+
// Reading URL parameters (query strings) (like /users?filter=active)
|
|
635
|
+
const filter = req.query.filter;
|
|
419
636
|
|
|
420
|
-
|
|
421
|
-
|
|
637
|
+
// Reading JSON request body
|
|
638
|
+
const anything = req.body.anything;
|
|
422
639
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
640
|
+
// Handling errors
|
|
641
|
+
if (anything === "not-expected-thing")
|
|
642
|
+
throw { status: 400, message: "Invalid property." };
|
|
426
643
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
644
|
+
// Sending a JSON response
|
|
645
|
+
res.status(200).json({ message: "This is a test response" });
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Reading and setting cookies
|
|
649
|
+
server.route("post", "/login", (req, res) => {
|
|
650
|
+
// Reads are available via req.cookies and req.signedCookies
|
|
651
|
+
const sessionId = req.signedCookies.sessionId;
|
|
652
|
+
|
|
653
|
+
// Set a signed session cookie
|
|
654
|
+
res.cookie("sessionId", "abc123", { signed: true, httpOnly: true, secure: true });
|
|
655
|
+
res.status(200).json({ message: "Logged in" });
|
|
656
|
+
});
|
|
431
657
|
|
|
432
658
|
// Sending a file response
|
|
433
659
|
server.route("get", "/file", (req, res) => {
|
|
@@ -442,7 +668,7 @@ server.handleErr((error, req, res) => {
|
|
|
442
668
|
} else {
|
|
443
669
|
console.error(error);
|
|
444
670
|
res.status(500).json({
|
|
445
|
-
error: "Sorry, something unexpected happened from our side."
|
|
671
|
+
error: "Sorry, something unexpected happened from our side."
|
|
446
672
|
});
|
|
447
673
|
}
|
|
448
674
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
1
2
|
import http, { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
3
|
|
|
3
|
-
type Cpeak$1 = ReturnType<typeof cpeak>;
|
|
4
4
|
type StringMap = Record<string, string>;
|
|
5
|
-
interface CpeakRequest<ReqBody = any,
|
|
6
|
-
params:
|
|
7
|
-
|
|
5
|
+
interface CpeakRequest<ReqBody = any, ReqQueries = any> extends IncomingMessage {
|
|
6
|
+
params: StringMap;
|
|
7
|
+
query: ReqQueries;
|
|
8
8
|
body?: ReqBody;
|
|
9
|
+
cookies?: StringMap;
|
|
10
|
+
signedCookies?: Record<string, string | false>;
|
|
9
11
|
[key: string]: any;
|
|
10
|
-
query: ReqParams;
|
|
11
12
|
}
|
|
12
13
|
interface CpeakResponse extends ServerResponse {
|
|
13
14
|
sendFile: (path: string, mime: string) => Promise<void>;
|
|
14
15
|
status: (code: number) => CpeakResponse;
|
|
15
|
-
|
|
16
|
+
attachment: (filename?: string) => CpeakResponse;
|
|
17
|
+
cookie: (name: string, value: string, options?: any) => CpeakResponse;
|
|
18
|
+
redirect: (location: string) => void;
|
|
16
19
|
json: (data: any) => void;
|
|
17
20
|
[key: string]: any;
|
|
18
21
|
}
|
|
@@ -31,35 +34,91 @@ interface RoutesMap {
|
|
|
31
34
|
[method: string]: Route[];
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
declare const
|
|
37
|
+
declare const parseJSON: (options?: {
|
|
38
|
+
limit?: number;
|
|
39
|
+
}) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
35
40
|
|
|
36
|
-
declare const
|
|
41
|
+
declare const serveStatic: (folderPath: string, newMimeTypes?: StringMap, options?: {
|
|
42
|
+
prefix?: string;
|
|
43
|
+
}) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void | Promise<void>;
|
|
37
44
|
|
|
38
45
|
declare const render: () => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
39
46
|
|
|
40
|
-
declare
|
|
47
|
+
declare const swagger: (spec: object, prefix?: string) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
48
|
+
|
|
49
|
+
interface PbkdfOptions {
|
|
50
|
+
iterations?: number;
|
|
51
|
+
keylen?: number;
|
|
52
|
+
digest?: string;
|
|
53
|
+
saltSize?: number;
|
|
54
|
+
}
|
|
55
|
+
interface AuthOptions extends PbkdfOptions {
|
|
56
|
+
secret: string;
|
|
57
|
+
saveToken: (tokenId: string, userId: string, expiresAt: Date) => Promise<void>;
|
|
58
|
+
findToken: (tokenId: string) => Promise<{
|
|
59
|
+
userId: string;
|
|
60
|
+
expiresAt: Date;
|
|
61
|
+
} | null>;
|
|
62
|
+
tokenExpiry?: number;
|
|
63
|
+
hmacAlgorithm?: string;
|
|
64
|
+
tokenIdSize?: number;
|
|
65
|
+
revokeToken?: (tokenId: string) => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
declare function hashPassword(password: string, options?: PbkdfOptions): Promise<string>;
|
|
68
|
+
declare function verifyPassword(password: string, stored: string): Promise<boolean>;
|
|
69
|
+
declare function auth(options: AuthOptions): Middleware;
|
|
70
|
+
|
|
71
|
+
interface CookieOptions {
|
|
72
|
+
signed?: boolean;
|
|
73
|
+
httpOnly?: boolean;
|
|
74
|
+
secure?: boolean;
|
|
75
|
+
sameSite?: "strict" | "lax" | "none";
|
|
76
|
+
maxAge?: number;
|
|
77
|
+
expires?: Date;
|
|
78
|
+
path?: string;
|
|
79
|
+
domain?: string;
|
|
80
|
+
}
|
|
81
|
+
declare function cookieParser(options?: {
|
|
82
|
+
secret?: string;
|
|
83
|
+
}): (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
84
|
+
|
|
85
|
+
declare function frameworkError(message: string, skipFn: Function, code?: string, status?: number): Error & {
|
|
41
86
|
code?: string;
|
|
87
|
+
cpeak_err?: boolean;
|
|
42
88
|
};
|
|
43
89
|
declare enum ErrorCode {
|
|
44
90
|
MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
|
|
45
91
|
FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
|
|
46
92
|
NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
|
|
47
|
-
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL"
|
|
93
|
+
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
|
|
94
|
+
INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
|
|
95
|
+
PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
|
|
96
|
+
WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET"
|
|
97
|
+
}
|
|
98
|
+
declare class CpeakIncomingMessage extends http.IncomingMessage {
|
|
99
|
+
#private;
|
|
100
|
+
body: any;
|
|
101
|
+
params: StringMap;
|
|
102
|
+
get query(): StringMap;
|
|
103
|
+
}
|
|
104
|
+
declare class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
105
|
+
sendFile(path: string, mime: string): Promise<void>;
|
|
106
|
+
status(code: number): this;
|
|
107
|
+
attachment(filename?: string): this;
|
|
108
|
+
redirect(location: string): void;
|
|
109
|
+
json(data: any): void;
|
|
48
110
|
}
|
|
49
111
|
declare class Cpeak {
|
|
50
112
|
#private;
|
|
51
|
-
private server;
|
|
52
|
-
private routes;
|
|
53
|
-
private middleware;
|
|
54
|
-
private _handleErr?;
|
|
55
113
|
constructor();
|
|
56
114
|
route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]): void;
|
|
57
115
|
beforeEach(cb: Middleware): void;
|
|
58
116
|
handleErr(cb: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void): void;
|
|
59
|
-
listen(port: number, cb?: () => void): http.Server<typeof
|
|
117
|
+
listen(port: number, cb?: () => void): http.Server<typeof CpeakIncomingMessage, typeof CpeakServerResponse>;
|
|
118
|
+
address(): string | net.AddressInfo | null;
|
|
60
119
|
close(cb?: (err?: Error) => void): void;
|
|
61
120
|
}
|
|
62
121
|
|
|
63
122
|
declare function cpeak(): Cpeak;
|
|
64
123
|
|
|
65
|
-
export { type
|
|
124
|
+
export { type AuthOptions, type CookieOptions, Cpeak, CpeakIncomingMessage, type CpeakRequest, type CpeakResponse, CpeakServerResponse, ErrorCode, type HandleErr, type Handler, type Middleware, type Next, type PbkdfOptions, type RouteMiddleware, type RoutesMap, auth, cookieParser, cpeak as default, frameworkError, hashPassword, parseJSON, render, serveStatic, swagger, verifyPassword };
|