skyguard-js 1.1.2 → 1.1.3
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 +35 -3
- package/dist/app.js +3 -2
- package/dist/crypto/jwt.d.ts +26 -12
- package/dist/crypto/jwt.js +56 -30
- package/dist/http/request.d.ts +12 -0
- package/dist/http/request.js +19 -0
- package/dist/http/response.d.ts +9 -0
- package/dist/http/response.js +27 -0
- package/dist/middlewares/cors.d.ts +1 -18
- package/dist/middlewares/session.d.ts +38 -16
- package/dist/middlewares/session.js +63 -61
- package/dist/parsers/jsonParser.js +1 -2
- package/dist/parsers/multipartParser.d.ts +1 -1
- package/dist/parsers/multipartParser.js +3 -4
- package/dist/parsers/parserInterface.d.ts +2 -13
- package/dist/parsers/parserInterface.js +0 -14
- package/dist/parsers/textParser.js +2 -3
- package/dist/parsers/urlEncodedParser.js +1 -2
- package/dist/parsers/xmlParser.js +2 -3
- package/dist/routing/layer.js +5 -1
- package/dist/sessions/cookies.d.ts +120 -0
- package/dist/sessions/cookies.js +92 -0
- package/dist/sessions/fileSessionStorage.d.ts +133 -81
- package/dist/sessions/fileSessionStorage.js +199 -135
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.js +4 -1
- package/dist/sessions/memorySessionStorage.d.ts +108 -59
- package/dist/sessions/memorySessionStorage.js +156 -73
- package/dist/sessions/session.d.ts +11 -71
- package/dist/sessions/session.js +23 -72
- package/dist/sessions/sessionStorage.d.ts +24 -65
- package/dist/sessions/sessionStorage.js +3 -0
- package/dist/static/fileStaticHandler.js +1 -1
- package/dist/storage/storage.d.ts +17 -31
- package/dist/storage/storage.js +102 -35
- package/dist/storage/types.d.ts +23 -6
- package/dist/storage/uploader.js +24 -8
- package/dist/views/engineTemplate.js +4 -3
- package/package.json +1 -1
- package/dist/sessions/cookieOptions.d.ts +0 -72
- package/dist/sessions/cookieOptions.js +0 -2
package/README.md
CHANGED
|
@@ -242,7 +242,20 @@ To handle sessions, you must use the framework’s built-in middleware. Dependin
|
|
|
242
242
|
import { sessions } from "skyguard-js/middlewares";
|
|
243
243
|
import { FileSessionStorage } from "skyguard-js";
|
|
244
244
|
|
|
245
|
-
app.middlewares([
|
|
245
|
+
app.middlewares([
|
|
246
|
+
sessions(FileSessionStorage, {
|
|
247
|
+
name: "connect.sid",
|
|
248
|
+
rolling: true,
|
|
249
|
+
saveUninitialized: false,
|
|
250
|
+
cookie: {
|
|
251
|
+
maxAge: 60 * 60 * 24,
|
|
252
|
+
httpOnly: true,
|
|
253
|
+
sameSite: "Lax",
|
|
254
|
+
secure: false,
|
|
255
|
+
path: "/",
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
]);
|
|
246
259
|
|
|
247
260
|
app.post("/login", (request: Request) => {
|
|
248
261
|
const { username, password } = request.data;
|
|
@@ -300,10 +313,25 @@ app.post("/login", async (request: Request) => {
|
|
|
300
313
|
throw new UnauthorizedError("Invalid credentials");
|
|
301
314
|
}
|
|
302
315
|
|
|
303
|
-
const token = createJWT(
|
|
316
|
+
const token = createJWT(
|
|
317
|
+
{ sub: user.id, role: user.role },
|
|
318
|
+
"secret-key",
|
|
319
|
+
3600,
|
|
320
|
+
);
|
|
304
321
|
|
|
305
322
|
return Response.json({ token });
|
|
306
323
|
});
|
|
324
|
+
|
|
325
|
+
app.get(
|
|
326
|
+
"/verify-jwt",
|
|
327
|
+
(request: Request) => {
|
|
328
|
+
// The request stores a user state; the middleware creates and assigns this state to the request.state object.
|
|
329
|
+
const user = request.state.user;
|
|
330
|
+
|
|
331
|
+
return json({ user });
|
|
332
|
+
},
|
|
333
|
+
[authJWT("secret-key")],
|
|
334
|
+
);
|
|
307
335
|
```
|
|
308
336
|
|
|
309
337
|
---
|
|
@@ -318,7 +346,9 @@ import { createUploader, StorageType } from "skyguard-js";
|
|
|
318
346
|
const uploader = createUploader({
|
|
319
347
|
storageType: StorageType.DISK,
|
|
320
348
|
storageOptions: {
|
|
321
|
-
|
|
349
|
+
disk: {
|
|
350
|
+
destination: "./uploads",
|
|
351
|
+
},
|
|
322
352
|
},
|
|
323
353
|
});
|
|
324
354
|
|
|
@@ -334,6 +364,8 @@ app.post(
|
|
|
334
364
|
);
|
|
335
365
|
```
|
|
336
366
|
|
|
367
|
+
Depending on the `Storage Type` you have selected, the storage options will contain two properties: `disk` and `memory`
|
|
368
|
+
|
|
337
369
|
---
|
|
338
370
|
|
|
339
371
|
## 📄 Views & Template Engine
|
package/dist/app.js
CHANGED
|
@@ -50,8 +50,8 @@ class App {
|
|
|
50
50
|
*/
|
|
51
51
|
static bootstrap() {
|
|
52
52
|
const app = container_1.Container.singleton(App);
|
|
53
|
-
app.router =
|
|
54
|
-
app.logger =
|
|
53
|
+
app.router = container_1.Container.singleton(routing_1.Router);
|
|
54
|
+
app.logger = container_1.Container.singleton(http_1.Logger);
|
|
55
55
|
app.viewEngine = container_1.Container.singleton(engineTemplate_1.ViewEngine);
|
|
56
56
|
return app;
|
|
57
57
|
}
|
|
@@ -228,6 +228,7 @@ class App {
|
|
|
228
228
|
statusCode: 500,
|
|
229
229
|
message: "Internal Server Error",
|
|
230
230
|
}).setStatusCode(500));
|
|
231
|
+
// eslint-disable-next-line no-console
|
|
231
232
|
console.error(error);
|
|
232
233
|
}
|
|
233
234
|
}
|
package/dist/crypto/jwt.d.ts
CHANGED
|
@@ -8,24 +8,38 @@ interface JWTHeader {
|
|
|
8
8
|
typ: string;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Creates a JSON Web Token (JWT) signed using HMAC-SHA256 (HS256).
|
|
12
|
+
*
|
|
13
|
+
* Note:
|
|
14
|
+
* This implementation produces **stateless signed tokens** and does not
|
|
15
|
+
* encrypt the payload. Anyone can decode the payload, but only holders of
|
|
16
|
+
* the secret can generate a valid signature.
|
|
17
|
+
*
|
|
18
|
+
* @param payload - Custom claims to include in the token.
|
|
19
|
+
* @param secret - Secret key used to sign the token.
|
|
20
|
+
* @param expiresIn - Optional expiration time in seconds.
|
|
21
|
+
* @returns Signed JWT string.
|
|
16
22
|
*/
|
|
17
23
|
export declare const createJWT: (payload: JWTPayload, secret: string, expiresIn?: number) => string;
|
|
18
24
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
25
|
+
* Verifies the integrity and validity of a JSON Web Token.
|
|
26
|
+
*
|
|
27
|
+
* If any validation step fails, the function returns `null`.
|
|
28
|
+
*
|
|
29
|
+
* @param token - JWT string to verify.
|
|
30
|
+
* @param secret - Secret used to sign the token.
|
|
31
|
+
* @returns Decoded payload if the token is valid, otherwise `null`.
|
|
23
32
|
*/
|
|
24
33
|
export declare const verifyJWT: (token: string, secret: string) => JWTPayload | null;
|
|
25
34
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
35
|
+
* Decodes a JWT without verifying its signature.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ Security warning:
|
|
38
|
+
* This function MUST NOT be used for authentication or authorization.
|
|
39
|
+
* It is intended only for debugging, logging, or inspecting token contents.
|
|
40
|
+
*
|
|
41
|
+
* @param token - JWT string to decode.
|
|
42
|
+
* @returns Object containing decoded header and payload, or `null` if malformed.
|
|
29
43
|
*/
|
|
30
44
|
export declare const decodeJWT: (token: string) => {
|
|
31
45
|
header: JWTHeader;
|
package/dist/crypto/jwt.js
CHANGED
|
@@ -3,7 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.decodeJWT = exports.verifyJWT = exports.createJWT = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Encodes a string using Base64URL encoding (RFC 7515).
|
|
7
|
+
*
|
|
8
|
+
* This format is required by JWT to ensure the token is URL-safe.
|
|
9
|
+
*
|
|
10
|
+
* @param str - UTF-8 string to encode.
|
|
11
|
+
* @returns Base64URL encoded string without padding.
|
|
7
12
|
*/
|
|
8
13
|
function base64UrlEncode(str) {
|
|
9
14
|
return Buffer.from(str)
|
|
@@ -13,7 +18,13 @@ function base64UrlEncode(str) {
|
|
|
13
18
|
.replace(/=/g, "");
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
|
-
*
|
|
21
|
+
* Decodes a Base64URL string back to a UTF-8 string.
|
|
22
|
+
*
|
|
23
|
+
* The function restores the standard Base64 alphabet and padding
|
|
24
|
+
* before performing the decoding.
|
|
25
|
+
*
|
|
26
|
+
* @param str - Base64URL encoded string.
|
|
27
|
+
* @returns Decoded UTF-8 string.
|
|
17
28
|
*/
|
|
18
29
|
function base64UrlDecode(str) {
|
|
19
30
|
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -23,11 +34,17 @@ function base64UrlDecode(str) {
|
|
|
23
34
|
return Buffer.from(base64, "base64").toString("utf-8");
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* Creates a JSON Web Token (JWT) signed using HMAC-SHA256 (HS256).
|
|
38
|
+
*
|
|
39
|
+
* Note:
|
|
40
|
+
* This implementation produces **stateless signed tokens** and does not
|
|
41
|
+
* encrypt the payload. Anyone can decode the payload, but only holders of
|
|
42
|
+
* the secret can generate a valid signature.
|
|
43
|
+
*
|
|
44
|
+
* @param payload - Custom claims to include in the token.
|
|
45
|
+
* @param secret - Secret key used to sign the token.
|
|
46
|
+
* @param expiresIn - Optional expiration time in seconds.
|
|
47
|
+
* @returns Signed JWT string.
|
|
31
48
|
*/
|
|
32
49
|
const createJWT = (payload, secret, expiresIn) => {
|
|
33
50
|
const header = {
|
|
@@ -50,33 +67,37 @@ const createJWT = (payload, secret, expiresIn) => {
|
|
|
50
67
|
.replace(/\+/g, "-")
|
|
51
68
|
.replace(/\//g, "_")
|
|
52
69
|
.replace(/=/g, "");
|
|
53
|
-
// Retornar JWT completo
|
|
54
70
|
return `${data}.${signature}`;
|
|
55
71
|
};
|
|
56
72
|
exports.createJWT = createJWT;
|
|
57
73
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
74
|
+
* Verifies the integrity and validity of a JSON Web Token.
|
|
75
|
+
*
|
|
76
|
+
* If any validation step fails, the function returns `null`.
|
|
77
|
+
*
|
|
78
|
+
* @param token - JWT string to verify.
|
|
79
|
+
* @param secret - Secret used to sign the token.
|
|
80
|
+
* @returns Decoded payload if the token is valid, otherwise `null`.
|
|
62
81
|
*/
|
|
63
82
|
const verifyJWT = (token, secret) => {
|
|
64
|
-
const parts = token.split(".");
|
|
65
|
-
if (parts.length !== 3)
|
|
66
|
-
return null;
|
|
67
|
-
const [encodedHeader, encodedPayload, signature] = parts;
|
|
68
|
-
const data = `${encodedHeader}.${encodedPayload}`;
|
|
69
|
-
const expectedSignature = (0, node_crypto_1.createHmac)("sha256", secret)
|
|
70
|
-
.update(data)
|
|
71
|
-
.digest("base64")
|
|
72
|
-
.replace(/\+/g, "-")
|
|
73
|
-
.replace(/\//g, "_")
|
|
74
|
-
.replace(/=/g, "");
|
|
75
|
-
const signatureBuffer = Buffer.from(signature);
|
|
76
|
-
const computedBuffer = Buffer.from(expectedSignature);
|
|
77
|
-
if (!(0, node_crypto_1.timingSafeEqual)(signatureBuffer, computedBuffer))
|
|
78
|
-
return null;
|
|
79
83
|
try {
|
|
84
|
+
const parts = token.split(".");
|
|
85
|
+
if (parts.length !== 3)
|
|
86
|
+
return null;
|
|
87
|
+
const [encodedHeader, encodedPayload, signature] = parts;
|
|
88
|
+
const data = `${encodedHeader}.${encodedPayload}`;
|
|
89
|
+
const expectedSignature = (0, node_crypto_1.createHmac)("sha256", secret)
|
|
90
|
+
.update(data)
|
|
91
|
+
.digest("base64")
|
|
92
|
+
.replace(/\+/g, "-")
|
|
93
|
+
.replace(/\//g, "_")
|
|
94
|
+
.replace(/=/g, "");
|
|
95
|
+
const signatureBuffer = Buffer.from(signature);
|
|
96
|
+
const computedBuffer = Buffer.from(expectedSignature);
|
|
97
|
+
if (signatureBuffer.length !== computedBuffer.length)
|
|
98
|
+
return null;
|
|
99
|
+
if (!(0, node_crypto_1.timingSafeEqual)(signatureBuffer, computedBuffer))
|
|
100
|
+
return null;
|
|
80
101
|
const payload = JSON.parse(base64UrlDecode(encodedPayload));
|
|
81
102
|
if (!payload.exp)
|
|
82
103
|
return null;
|
|
@@ -91,9 +112,14 @@ const verifyJWT = (token, secret) => {
|
|
|
91
112
|
};
|
|
92
113
|
exports.verifyJWT = verifyJWT;
|
|
93
114
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
115
|
+
* Decodes a JWT without verifying its signature.
|
|
116
|
+
*
|
|
117
|
+
* ⚠️ Security warning:
|
|
118
|
+
* This function MUST NOT be used for authentication or authorization.
|
|
119
|
+
* It is intended only for debugging, logging, or inspecting token contents.
|
|
120
|
+
*
|
|
121
|
+
* @param token - JWT string to decode.
|
|
122
|
+
* @returns Object containing decoded header and payload, or `null` if malformed.
|
|
97
123
|
*/
|
|
98
124
|
const decodeJWT = (token) => {
|
|
99
125
|
try {
|
package/dist/http/request.d.ts
CHANGED
|
@@ -55,6 +55,18 @@ export declare class Request {
|
|
|
55
55
|
setData(data: Record<string, any>): this;
|
|
56
56
|
get session(): Session;
|
|
57
57
|
setSession(session: Session): void;
|
|
58
|
+
/**
|
|
59
|
+
* Returns all request cookies as a key-value object.
|
|
60
|
+
*/
|
|
61
|
+
get cookies(): Record<string, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Returns a cookie value by name.
|
|
64
|
+
*/
|
|
65
|
+
getCookie(name: string): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Checks whether a cookie exists.
|
|
68
|
+
*/
|
|
69
|
+
hasCookie(name: string): boolean;
|
|
58
70
|
/**
|
|
59
71
|
* Validates the request payload against a validation schema.
|
|
60
72
|
*
|
package/dist/http/request.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Request = void 0;
|
|
4
4
|
const validators_1 = require("../validators");
|
|
5
|
+
const cookies_1 = require("../sessions/cookies");
|
|
5
6
|
/**
|
|
6
7
|
* Represents an incoming client request within the framework.
|
|
7
8
|
*
|
|
@@ -87,6 +88,24 @@ class Request {
|
|
|
87
88
|
setSession(session) {
|
|
88
89
|
this._session = session;
|
|
89
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Returns all request cookies as a key-value object.
|
|
93
|
+
*/
|
|
94
|
+
get cookies() {
|
|
95
|
+
return (0, cookies_1.parseCookies)(this._headers?.cookie);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns a cookie value by name.
|
|
99
|
+
*/
|
|
100
|
+
getCookie(name) {
|
|
101
|
+
return this.cookies[name];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Checks whether a cookie exists.
|
|
105
|
+
*/
|
|
106
|
+
hasCookie(name) {
|
|
107
|
+
return name in this.cookies;
|
|
108
|
+
}
|
|
90
109
|
/**
|
|
91
110
|
* Validates the request payload against a validation schema.
|
|
92
111
|
*
|
package/dist/http/response.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Headers } from "../types";
|
|
2
|
+
import { type CookieOptions } from "../sessions/cookies";
|
|
2
3
|
/**
|
|
3
4
|
* Represents an outgoing response sent to the client.
|
|
4
5
|
*
|
|
@@ -35,6 +36,14 @@ export declare class Response {
|
|
|
35
36
|
private merge;
|
|
36
37
|
setHeader(header: string, value: string): this;
|
|
37
38
|
removeHeader(header: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Sets a cookie in the `Set-Cookie` response header.
|
|
41
|
+
*/
|
|
42
|
+
setCookie(name: string, value: string, options?: CookieOptions): this;
|
|
43
|
+
/**
|
|
44
|
+
* Clears a cookie by setting an empty value and immediate expiration.
|
|
45
|
+
*/
|
|
46
|
+
removeCookie(name: string, options?: CookieOptions): this;
|
|
38
47
|
/**
|
|
39
48
|
* Semantic shortcut to define the `Content-Type` header.
|
|
40
49
|
*
|
package/dist/http/response.js
CHANGED
|
@@ -6,6 +6,7 @@ const invalidHttpStatusException_1 = require("../exceptions/invalidHttpStatusExc
|
|
|
6
6
|
const fileDownload_1 = require("../static/fileDownload");
|
|
7
7
|
const engineTemplate_1 = require("../views/engineTemplate");
|
|
8
8
|
const container_1 = require("../container/container");
|
|
9
|
+
const cookies_1 = require("../sessions/cookies");
|
|
9
10
|
/**
|
|
10
11
|
* Represents an outgoing response sent to the client.
|
|
11
12
|
*
|
|
@@ -62,6 +63,32 @@ class Response {
|
|
|
62
63
|
removeHeader(header) {
|
|
63
64
|
delete this._headers[header];
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Sets a cookie in the `Set-Cookie` response header.
|
|
68
|
+
*/
|
|
69
|
+
setCookie(name, value, options = {}) {
|
|
70
|
+
const cookie = (0, cookies_1.serializeCookie)(name, value, options);
|
|
71
|
+
const current = this._headers["Set-Cookie"];
|
|
72
|
+
if (!current) {
|
|
73
|
+
this._headers["Set-Cookie"] = cookie;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(current)) {
|
|
77
|
+
this._headers["Set-Cookie"] = [...current, cookie];
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
this._headers["Set-Cookie"] = [current, cookie];
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Clears a cookie by setting an empty value and immediate expiration.
|
|
85
|
+
*/
|
|
86
|
+
removeCookie(name, options = {}) {
|
|
87
|
+
return this.setCookie(name, "", {
|
|
88
|
+
...options,
|
|
89
|
+
maxAge: 0,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
65
92
|
/**
|
|
66
93
|
* Semantic shortcut to define the `Content-Type` header.
|
|
67
94
|
*
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import { HttpMethods } from "../http";
|
|
2
2
|
import type { Middleware } from "../types";
|
|
3
|
-
/**
|
|
4
|
-
* CORS origin matcher type.
|
|
5
|
-
*
|
|
6
|
-
* Controls how the middleware determines the value of
|
|
7
|
-
* `Access-Control-Allow-Origin` for a given request.
|
|
8
|
-
*
|
|
9
|
-
* Supported forms:
|
|
10
|
-
* - `string`: a single allowed origin (e.g. `"https://example.com"`) or `"*"` for public access
|
|
11
|
-
* - `string[]`: a whitelist of allowed origins
|
|
12
|
-
* - `function`: a custom resolver that receives the request Origin and decides what to allow
|
|
13
|
-
*
|
|
14
|
-
* Notes:
|
|
15
|
-
* - If `credentials: true` is enabled, `"*"` cannot be used as the final
|
|
16
|
-
* `Access-Control-Allow-Origin` value. In that case this middleware will
|
|
17
|
-
* reflect the request origin when allowed.
|
|
18
|
-
*/
|
|
19
|
-
type CorsOrigin = string | string[] | ((origin: string | undefined) => string);
|
|
20
3
|
/**
|
|
21
4
|
* CORS middleware configuration options.
|
|
22
5
|
*
|
|
@@ -33,7 +16,7 @@ interface CorsOptions {
|
|
|
33
16
|
*
|
|
34
17
|
* Affects: `Access-Control-Allow-Origin`.
|
|
35
18
|
*/
|
|
36
|
-
origin?:
|
|
19
|
+
origin?: string | string[] | ((origin: string | undefined) => string);
|
|
37
20
|
/**
|
|
38
21
|
* HTTP methods allowed for cross-origin requests.
|
|
39
22
|
*
|
|
@@ -1,26 +1,48 @@
|
|
|
1
|
-
import { type SessionStorage
|
|
1
|
+
import { type SessionStorage } from "../sessions";
|
|
2
|
+
import { type CookieOptions } from "../sessions/cookies";
|
|
2
3
|
import type { Middleware } from "../types";
|
|
3
4
|
/**
|
|
4
5
|
* Constructor type for `SessionStorage` implementations.
|
|
5
|
-
*
|
|
6
|
-
* Allows injecting different session storage strategies
|
|
7
|
-
* (memory, file, redis, etc.).
|
|
8
6
|
*/
|
|
9
7
|
type SessionStorageConstructor<T extends SessionStorage = SessionStorage> = new (...args: any[]) => T;
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
9
|
+
* Options inspired by express-session and adapted to Skyguard.
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Name of the cookie that carries the session id.
|
|
14
|
+
* @default "connect.sid"
|
|
15
|
+
*/
|
|
16
|
+
name?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Enables issuing a fresh session cookie on every response.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
rolling?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Save a cookie even if the session was never initialized with data.
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
saveUninitialized?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Session cookie attributes.
|
|
29
|
+
*/
|
|
30
|
+
cookie?: CookieOptions;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Session lifecycle middleware.
|
|
34
|
+
*
|
|
35
|
+
* API inspired by `express-session`, adapted to the Skyguard architecture.
|
|
12
36
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
37
|
+
* Cookie emission rules:
|
|
38
|
+
* - A cookie is set when a session id exists AND at least one of the following is true:
|
|
39
|
+
* - `rolling` is enabled (refresh cookie every request),
|
|
40
|
+
* - `saveUninitialized` is enabled (always create/set cookie when no prior session),
|
|
41
|
+
* - the session was created during this request.
|
|
16
42
|
*
|
|
17
|
-
* @
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* cookieName: "sid",
|
|
21
|
-
* maxAge: 86400,
|
|
22
|
-
* }),
|
|
23
|
-
* ]);
|
|
43
|
+
* @param StorageClass - Session storage constructor used per request.
|
|
44
|
+
* @param options - Raw session middleware options.
|
|
45
|
+
* @returns A Skyguard `Middleware` that manages session load/save and cookie IO.
|
|
24
46
|
*/
|
|
25
|
-
export declare const sessions: (StorageClass: SessionStorageConstructor, options?:
|
|
47
|
+
export declare const sessions: (StorageClass: SessionStorageConstructor, options?: SessionOptions) => Middleware;
|
|
26
48
|
export {};
|
|
@@ -2,85 +2,87 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sessions = void 0;
|
|
4
4
|
const sessions_1 = require("../sessions");
|
|
5
|
+
const cookies_1 = require("../sessions/cookies");
|
|
6
|
+
const httpExceptions_1 = require("../exceptions/httpExceptions");
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
8
|
+
* Attempts to load a session from a cookie value into the provided storage.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* @returns Parsed cookies
|
|
10
|
+
* If `cookieValue` is missing, this is a no-op.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* // { foo: "bar", session_id: "abc123" }
|
|
14
|
-
*/
|
|
15
|
-
function parseCookies(cookieHeader) {
|
|
16
|
-
if (!cookieHeader)
|
|
17
|
-
return {};
|
|
18
|
-
return Object.fromEntries(cookieHeader.split(";").map(cookie => {
|
|
19
|
-
const [key, ...value] = cookie.trim().split("=");
|
|
20
|
-
return [key, decodeURIComponent(value.join("="))];
|
|
21
|
-
}));
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Builds the `Set-Cookie` header value for the session.
|
|
12
|
+
* If the storage rejects with `UnauthorizedError` (e.g., invalid or expired session),
|
|
13
|
+
* the error is swallowed and the request proceeds as unauthenticated/uninitialized.
|
|
25
14
|
*
|
|
26
|
-
*
|
|
27
|
-
* @param config - Fully resolved cookie configuration
|
|
28
|
-
* @returns Value ready to be used in `Set-Cookie`
|
|
15
|
+
* Any other error type is treated as unexpected and is re-thrown.
|
|
29
16
|
*
|
|
30
|
-
* @
|
|
31
|
-
*
|
|
32
|
-
*
|
|
17
|
+
* @param storage - Session storage instance used to load the session.
|
|
18
|
+
* @param cookieValue - Session id extracted from the request cookie.
|
|
19
|
+
* @throws Unknown errors coming from the storage layer (non-UnauthorizedError).
|
|
33
20
|
*/
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return parts.join("; ");
|
|
21
|
+
async function loadSessionFromCookie(storage, cookieValue) {
|
|
22
|
+
if (!cookieValue)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
await storage.load(cookieValue);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof httpExceptions_1.UnauthorizedError)
|
|
29
|
+
return;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
46
32
|
}
|
|
47
33
|
/**
|
|
48
|
-
* Session lifecycle middleware
|
|
34
|
+
* Session lifecycle middleware.
|
|
35
|
+
*
|
|
36
|
+
* API inspired by `express-session`, adapted to the Skyguard architecture.
|
|
49
37
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
38
|
+
* Cookie emission rules:
|
|
39
|
+
* - A cookie is set when a session id exists AND at least one of the following is true:
|
|
40
|
+
* - `rolling` is enabled (refresh cookie every request),
|
|
41
|
+
* - `saveUninitialized` is enabled (always create/set cookie when no prior session),
|
|
42
|
+
* - the session was created during this request.
|
|
53
43
|
*
|
|
54
|
-
* @
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* cookieName: "sid",
|
|
58
|
-
* maxAge: 86400,
|
|
59
|
-
* }),
|
|
60
|
-
* ]);
|
|
44
|
+
* @param StorageClass - Session storage constructor used per request.
|
|
45
|
+
* @param options - Raw session middleware options.
|
|
46
|
+
* @returns A Skyguard `Middleware` that manages session load/save and cookie IO.
|
|
61
47
|
*/
|
|
62
48
|
const sessions = (StorageClass, options = {}) => {
|
|
63
|
-
const timeMaxAge = 86400;
|
|
64
49
|
const config = {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
50
|
+
name: options.name ?? "connect.sid",
|
|
51
|
+
rolling: options.rolling ?? false,
|
|
52
|
+
saveUninitialized: options.saveUninitialized ?? false,
|
|
53
|
+
cookie: {
|
|
54
|
+
maxAge: options.cookie?.maxAge ?? 86400,
|
|
55
|
+
httpOnly: options.cookie?.httpOnly ?? true,
|
|
56
|
+
secure: options.cookie?.secure ?? false,
|
|
57
|
+
sameSite: options.cookie?.sameSite ?? "Lax",
|
|
58
|
+
path: options.cookie?.path ?? "/",
|
|
59
|
+
},
|
|
71
60
|
};
|
|
72
61
|
return async (request, next) => {
|
|
73
|
-
const cookies = parseCookies(request.headers.cookie
|
|
74
|
-
const
|
|
75
|
-
const storage = new StorageClass(
|
|
76
|
-
|
|
77
|
-
await storage.load(sessionId);
|
|
62
|
+
const cookies = (0, cookies_1.parseCookies)(request.headers.cookie);
|
|
63
|
+
const sessionIdFromCookie = cookies[config.name];
|
|
64
|
+
const storage = new StorageClass(config.cookie.maxAge);
|
|
65
|
+
await loadSessionFromCookie(storage, sessionIdFromCookie);
|
|
78
66
|
const session = new sessions_1.Session(storage);
|
|
79
67
|
request.setSession(session);
|
|
68
|
+
const sessionIdBefore = storage.id();
|
|
69
|
+
if (!sessionIdBefore && config.saveUninitialized)
|
|
70
|
+
await storage.start();
|
|
80
71
|
const response = await next(request);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
const sessionIdAfter = storage.id();
|
|
73
|
+
if (sessionIdAfter && config.rolling)
|
|
74
|
+
await storage.touch();
|
|
75
|
+
const sessionWasCreated = !sessionIdBefore && Boolean(sessionIdAfter);
|
|
76
|
+
const shouldSetCookie = Boolean(sessionIdAfter) &&
|
|
77
|
+
(config.rolling || config.saveUninitialized || sessionWasCreated);
|
|
78
|
+
if (shouldSetCookie && sessionIdAfter) {
|
|
79
|
+
response.setHeader("Set-Cookie", (0, cookies_1.serializeCookie)(config.name, sessionIdAfter, {
|
|
80
|
+
maxAge: config.cookie.maxAge,
|
|
81
|
+
path: config.cookie.path,
|
|
82
|
+
httpOnly: config.cookie.httpOnly,
|
|
83
|
+
secure: config.cookie.secure,
|
|
84
|
+
sameSite: config.cookie.sameSite,
|
|
85
|
+
}));
|
|
84
86
|
}
|
|
85
87
|
return response;
|
|
86
88
|
};
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.JsonParser = void 0;
|
|
4
4
|
const httpExceptions_1 = require("../exceptions/httpExceptions");
|
|
5
|
-
const parserInterface_1 = require("./parserInterface");
|
|
6
5
|
/**
|
|
7
6
|
* JSON content parser.
|
|
8
7
|
*
|
|
@@ -10,7 +9,7 @@ const parserInterface_1 = require("./parserInterface");
|
|
|
10
9
|
*/
|
|
11
10
|
class JsonParser {
|
|
12
11
|
canParse(contentType) {
|
|
13
|
-
return contentType.includes(
|
|
12
|
+
return contentType.includes("application/json");
|
|
14
13
|
}
|
|
15
14
|
parse(body) {
|
|
16
15
|
try {
|