skyguard-js 1.1.3 → 1.1.5
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 +30 -30
- package/dist/app.d.ts +9 -4
- package/dist/app.js +9 -7
- package/dist/crypto/hasher.d.ts +19 -10
- package/dist/crypto/hasher.js +21 -10
- package/dist/crypto/jwt.d.ts +55 -7
- package/dist/crypto/jwt.js +177 -26
- package/dist/http/logger.d.ts +47 -0
- package/dist/http/logger.js +47 -0
- package/dist/http/nodeNativeHttp.d.ts +4 -0
- package/dist/http/nodeNativeHttp.js +12 -4
- package/dist/http/request.d.ts +9 -23
- package/dist/http/request.js +3 -24
- package/dist/http/response.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/middlewares/cors.d.ts +13 -9
- package/dist/middlewares/cors.js +25 -15
- package/dist/middlewares/index.d.ts +0 -1
- package/dist/middlewares/index.js +1 -3
- package/dist/middlewares/session.d.ts +19 -9
- package/dist/middlewares/session.js +19 -9
- package/dist/parsers/contentParserManager.d.ts +0 -6
- package/dist/parsers/contentParserManager.js +0 -6
- package/dist/parsers/multipartParser.d.ts +85 -5
- package/dist/parsers/multipartParser.js +85 -5
- package/dist/parsers/xmlParser.d.ts +90 -0
- package/dist/parsers/xmlParser.js +90 -0
- package/dist/routing/layer.d.ts +0 -22
- package/dist/routing/layer.js +1 -26
- package/dist/routing/router.d.ts +0 -36
- package/dist/routing/router.js +0 -36
- package/dist/routing/routerGroup.d.ts +0 -15
- package/dist/routing/routerGroup.js +0 -15
- package/dist/sessions/cookies.d.ts +0 -29
- package/dist/sessions/cookies.js +0 -19
- package/dist/static/contentDisposition.d.ts +78 -19
- package/dist/static/contentDisposition.js +78 -19
- package/dist/static/fileDownload.d.ts +0 -14
- package/dist/static/fileDownload.js +0 -14
- package/dist/static/fileStaticHandler.d.ts +0 -11
- package/dist/static/fileStaticHandler.js +0 -11
- package/dist/storage/storage.js +1 -1
- package/dist/storage/uploader.d.ts +21 -0
- package/dist/storage/uploader.js +22 -1
- package/dist/validators/validationSchema.d.ts +16 -6
- package/dist/validators/validationSchema.js +24 -7
- package/dist/validators/validator.d.ts +1 -24
- package/dist/validators/validator.js +1 -24
- package/package.json +2 -2
- package/dist/middlewares/auth.d.ts +0 -34
- package/dist/middlewares/auth.js +0 -57
package/README.md
CHANGED
|
@@ -55,12 +55,14 @@ import { createApp, Response } from "skyguard-js";
|
|
|
55
55
|
|
|
56
56
|
const app = createApp();
|
|
57
57
|
|
|
58
|
+
const PORT = 3000;
|
|
59
|
+
|
|
58
60
|
app.get("/health", () => {
|
|
59
61
|
return Response.json({ status: "ok" });
|
|
60
62
|
});
|
|
61
63
|
|
|
62
|
-
app.run(
|
|
63
|
-
console.log(`Server running in port: http://localhost:${
|
|
64
|
+
app.run(PORT, () => {
|
|
65
|
+
console.log(`Server running in port: http://localhost:${PORT}`);
|
|
64
66
|
});
|
|
65
67
|
```
|
|
66
68
|
|
|
@@ -136,11 +138,12 @@ To enable CORS, use the built-in `cors` middleware.
|
|
|
136
138
|
|
|
137
139
|
```ts
|
|
138
140
|
import { cors } from "skyguard-js/middlewares";
|
|
141
|
+
import { HttpMethods } from "skyguard-js";
|
|
139
142
|
|
|
140
143
|
app.middlewares([
|
|
141
144
|
cors({
|
|
142
145
|
origin: ["http://localhost:3000", "https://myapp.com"],
|
|
143
|
-
methods: [
|
|
146
|
+
methods: [HttpMethods.get, HttpMethods.post],
|
|
144
147
|
allowedHeaders: ["Content-Type", "Authorization"],
|
|
145
148
|
credentials: true,
|
|
146
149
|
}),
|
|
@@ -165,11 +168,12 @@ app.staticFiles(join(__dirname, "..", "static"));
|
|
|
165
168
|
|
|
166
169
|
## ⛔ Data Validation
|
|
167
170
|
|
|
168
|
-
To validate data in the body of client requests, the framework provides the creation of validation
|
|
171
|
+
To validate the data in the body of client requests, the framework provides the creation of validation schemes and a middleware function to validate the body of HTTP requests, used as follows:
|
|
169
172
|
|
|
170
173
|
```ts
|
|
171
|
-
import { v, schema } from "skyguard-js";
|
|
174
|
+
import { v, schema, validateData } from "skyguard-js";
|
|
172
175
|
|
|
176
|
+
// Created Schema
|
|
173
177
|
const userSchema = schema({
|
|
174
178
|
name: v.string({ maxLength: 60 }),
|
|
175
179
|
email: v.email(),
|
|
@@ -178,17 +182,26 @@ const userSchema = schema({
|
|
|
178
182
|
birthdate: v.date({ max: new Date() }),
|
|
179
183
|
});
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
// Typing Interface
|
|
186
|
+
interface User {
|
|
187
|
+
name: string;
|
|
188
|
+
email: string;
|
|
189
|
+
age: number;
|
|
190
|
+
active: boolean;
|
|
191
|
+
birthdate: Date;
|
|
192
|
+
}
|
|
183
193
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
app.post(
|
|
195
|
+
"/test",
|
|
196
|
+
(request: Request) => {
|
|
197
|
+
const data = request.getData<User>();
|
|
198
|
+
return json(data).setStatusCode(201);
|
|
199
|
+
},
|
|
200
|
+
[validateData(userSchema)],
|
|
201
|
+
);
|
|
189
202
|
```
|
|
190
203
|
|
|
191
|
-
By default each property you define in the schema is required, to define it optional you use the `.optional()` or `.default(value)` function
|
|
204
|
+
To type the request body, an interface is used and the .getData() method is used, which allows returning the typed bodym. By default each property you define in the schema is required, to define it optional you use the `.optional()` or `.default(value)` function
|
|
192
205
|
|
|
193
206
|
Validation is:
|
|
194
207
|
|
|
@@ -289,7 +302,6 @@ The framework includes some password hashing and JWT token generation functions,
|
|
|
289
302
|
|
|
290
303
|
```ts
|
|
291
304
|
import { hash, verify, createJWT } from "skyguard-js/security";
|
|
292
|
-
import { authJWT } from "skyguard-js/middlewares";
|
|
293
305
|
|
|
294
306
|
app.post("/register", async (request: Request) => {
|
|
295
307
|
const { username, password } = request.data;
|
|
@@ -313,25 +325,13 @@ app.post("/login", async (request: Request) => {
|
|
|
313
325
|
throw new UnauthorizedError("Invalid credentials");
|
|
314
326
|
}
|
|
315
327
|
|
|
316
|
-
const token = createJWT(
|
|
317
|
-
|
|
318
|
-
"
|
|
319
|
-
|
|
320
|
-
);
|
|
328
|
+
const token = createJWT({ sub: "123" }, "secret-key", {
|
|
329
|
+
algorithm: "HS256",
|
|
330
|
+
expiresIn: "1h",
|
|
331
|
+
});
|
|
321
332
|
|
|
322
333
|
return Response.json({ token });
|
|
323
334
|
});
|
|
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
|
-
);
|
|
335
335
|
```
|
|
336
336
|
|
|
337
337
|
---
|
package/dist/app.d.ts
CHANGED
|
@@ -24,10 +24,6 @@ export declare class App {
|
|
|
24
24
|
private router;
|
|
25
25
|
/** Static file handler (optional) */
|
|
26
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
27
|
/** View engine for rendering templates (optional) */
|
|
32
28
|
private viewEngine;
|
|
33
29
|
/**
|
|
@@ -62,6 +58,7 @@ export declare class App {
|
|
|
62
58
|
*
|
|
63
59
|
* @example
|
|
64
60
|
* app.staticFiles("public");
|
|
61
|
+
* app.staticFiles(join(__dirname, "..", "public"));
|
|
65
62
|
* // /public/css/style.css → /css/style.css
|
|
66
63
|
*/
|
|
67
64
|
staticFiles(publicPath: string): void;
|
|
@@ -134,6 +131,14 @@ export declare class App {
|
|
|
134
131
|
* Registers global middlewares.
|
|
135
132
|
*
|
|
136
133
|
* These are executed for every route.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* const auth = async (request, next) => {
|
|
137
|
+
* console.log(request.header);
|
|
138
|
+
* return await next(request);
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* app.middlewares(auth);
|
|
137
142
|
*/
|
|
138
143
|
middlewares(middlewares: Middleware[]): void;
|
|
139
144
|
/**
|
package/dist/app.js
CHANGED
|
@@ -33,10 +33,6 @@ class App {
|
|
|
33
33
|
router;
|
|
34
34
|
/** Static file handler (optional) */
|
|
35
35
|
staticFileHandler = null;
|
|
36
|
-
/** Logger instance for request logging */
|
|
37
|
-
logger;
|
|
38
|
-
/** Timestamp marking the start of request processing (for logging) */
|
|
39
|
-
startTime;
|
|
40
36
|
/** View engine for rendering templates (optional) */
|
|
41
37
|
viewEngine;
|
|
42
38
|
/**
|
|
@@ -51,7 +47,6 @@ class App {
|
|
|
51
47
|
static bootstrap() {
|
|
52
48
|
const app = container_1.Container.singleton(App);
|
|
53
49
|
app.router = container_1.Container.singleton(routing_1.Router);
|
|
54
|
-
app.logger = container_1.Container.singleton(http_1.Logger);
|
|
55
50
|
app.viewEngine = container_1.Container.singleton(engineTemplate_1.ViewEngine);
|
|
56
51
|
return app;
|
|
57
52
|
}
|
|
@@ -93,6 +88,7 @@ class App {
|
|
|
93
88
|
*
|
|
94
89
|
* @example
|
|
95
90
|
* app.staticFiles("public");
|
|
91
|
+
* app.staticFiles(join(__dirname, "..", "public"));
|
|
96
92
|
* // /public/css/style.css → /css/style.css
|
|
97
93
|
*/
|
|
98
94
|
staticFiles(publicPath) {
|
|
@@ -148,10 +144,8 @@ class App {
|
|
|
148
144
|
*/
|
|
149
145
|
run(port, callback, hostname = "127.0.0.1") {
|
|
150
146
|
(0, node_http_1.createServer)((req, res) => {
|
|
151
|
-
this.startTime = process.hrtime.bigint();
|
|
152
147
|
const adapter = new http_1.NodeHttpAdapter(req, res);
|
|
153
148
|
void this.handle(adapter);
|
|
154
|
-
this.logger.log(req, res, this.startTime);
|
|
155
149
|
}).listen(port, hostname, () => {
|
|
156
150
|
callback();
|
|
157
151
|
});
|
|
@@ -192,6 +186,14 @@ class App {
|
|
|
192
186
|
* Registers global middlewares.
|
|
193
187
|
*
|
|
194
188
|
* These are executed for every route.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* const auth = async (request, next) => {
|
|
192
|
+
* console.log(request.header);
|
|
193
|
+
* return await next(request);
|
|
194
|
+
* }
|
|
195
|
+
*
|
|
196
|
+
* app.middlewares(auth);
|
|
195
197
|
*/
|
|
196
198
|
middlewares(middlewares) {
|
|
197
199
|
this.router.middlewares(middlewares);
|
package/dist/crypto/hasher.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ type ScryptOptions = {
|
|
|
15
15
|
* @param params - Scrypt work-factor params. Defaults to `DEFAULT_PARAMS`.
|
|
16
16
|
* @param pepper - Optional server secret mixed into the password (e.g., from env var).
|
|
17
17
|
* @returns A compact encoded hash string containing algorithm parameters + salt + derived key.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const passwordHash = await hash("password", 16);
|
|
18
21
|
*/
|
|
19
22
|
export declare const hash: (password: string, saltLength?: number, params?: ScryptOptions, pepper?: string) => Promise<string>;
|
|
20
23
|
/**
|
|
@@ -28,6 +31,9 @@ export declare const hash: (password: string, saltLength?: number, params?: Scry
|
|
|
28
31
|
* @param storedHash - Stored hash string in the compact format.
|
|
29
32
|
* @param pepper - Optional server secret; must match the one used when hashing.
|
|
30
33
|
* @returns `true` if the password matches, otherwise `false`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const validPassword = await verify("password", "passwordHashed");
|
|
31
37
|
*/
|
|
32
38
|
export declare const verify: (password: string, storedHash: string, pepper?: string) => Promise<boolean>;
|
|
33
39
|
/**
|
|
@@ -42,6 +48,9 @@ export declare const verify: (password: string, storedHash: string, pepper?: str
|
|
|
42
48
|
* @param storedHash - Stored hash string in compact format.
|
|
43
49
|
* @param params - Desired/current scrypt params. Defaults to `DEFAULT_PARAMS`.
|
|
44
50
|
* @returns `true` if the hash is missing/invalid or was produced with different parameters.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const passwordRehash = needsRehash("passwordHashed");
|
|
45
54
|
*/
|
|
46
55
|
export declare const needsRehash: (storedHash: string, params?: ScryptOptions) => boolean;
|
|
47
56
|
/**
|
|
@@ -62,13 +71,13 @@ export declare const needsRehash: (storedHash: string, params?: ScryptOptions) =
|
|
|
62
71
|
* - pepper: optional server secret
|
|
63
72
|
* - concurrency: max simultaneous operations (default 4)
|
|
64
73
|
* @returns Array of compact hash strings in the same order as input.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
*
|
|
77
|
+
* const listPasswords = ["password1", "password2", "password3"];
|
|
78
|
+
* const passwordsHasherList = await hashBatch(listPasswords, 16);
|
|
65
79
|
*/
|
|
66
|
-
export declare const hashBatch: (passwords: string[],
|
|
67
|
-
saltLength?: number;
|
|
68
|
-
params?: ScryptOptions;
|
|
69
|
-
pepper?: string;
|
|
70
|
-
concurrency?: number;
|
|
71
|
-
}) => Promise<string[]>;
|
|
80
|
+
export declare const hashBatch: (passwords: string[], saltLength?: number, concurrency?: number, params?: ScryptOptions, pepper?: string) => Promise<string[]>;
|
|
72
81
|
/**
|
|
73
82
|
* Verifies multiple password/hash pairs using controlled concurrency.
|
|
74
83
|
*
|
|
@@ -80,12 +89,12 @@ export declare const hashBatch: (passwords: string[], options?: {
|
|
|
80
89
|
* - pepper: optional server secret
|
|
81
90
|
* - concurrency: max simultaneous operations (default 8)
|
|
82
91
|
* @returns Array of booleans in the same order as input.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const verifyPasswords = await verifyBatch([{ password: "test", hash: "testHash" }, { password: "test2", hash: "testHash2" }]);
|
|
83
95
|
*/
|
|
84
96
|
export declare const verifyBatch: (credentials: Array<{
|
|
85
97
|
password: string;
|
|
86
98
|
hash: string;
|
|
87
|
-
}>,
|
|
88
|
-
pepper?: string;
|
|
89
|
-
concurrency?: number;
|
|
90
|
-
}) => Promise<boolean[]>;
|
|
99
|
+
}>, concurrency?: number, pepper?: string) => Promise<boolean[]>;
|
|
91
100
|
export {};
|
package/dist/crypto/hasher.js
CHANGED
|
@@ -87,6 +87,9 @@ const parseHash = (hash) => {
|
|
|
87
87
|
* @param params - Scrypt work-factor params. Defaults to `DEFAULT_PARAMS`.
|
|
88
88
|
* @param pepper - Optional server secret mixed into the password (e.g., from env var).
|
|
89
89
|
* @returns A compact encoded hash string containing algorithm parameters + salt + derived key.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const passwordHash = await hash("password", 16);
|
|
90
93
|
*/
|
|
91
94
|
const hash = async (password, saltLength = 16, params = DEFAULT_PARAMS, pepper) => {
|
|
92
95
|
const salt = (0, node_crypto_1.randomBytes)(saltLength);
|
|
@@ -111,6 +114,9 @@ exports.hash = hash;
|
|
|
111
114
|
* @param storedHash - Stored hash string in the compact format.
|
|
112
115
|
* @param pepper - Optional server secret; must match the one used when hashing.
|
|
113
116
|
* @returns `true` if the password matches, otherwise `false`.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const validPassword = await verify("password", "passwordHashed");
|
|
114
120
|
*/
|
|
115
121
|
const verify = async (password, storedHash, pepper) => {
|
|
116
122
|
const parsed = parseHash(storedHash);
|
|
@@ -145,6 +151,9 @@ exports.verify = verify;
|
|
|
145
151
|
* @param storedHash - Stored hash string in compact format.
|
|
146
152
|
* @param params - Desired/current scrypt params. Defaults to `DEFAULT_PARAMS`.
|
|
147
153
|
* @returns `true` if the hash is missing/invalid or was produced with different parameters.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const passwordRehash = needsRehash("passwordHashed");
|
|
148
157
|
*/
|
|
149
158
|
const needsRehash = (storedHash, params = DEFAULT_PARAMS) => {
|
|
150
159
|
const parsed = parseHash(storedHash);
|
|
@@ -191,13 +200,14 @@ const mapLimit = async (items, limit, fn) => {
|
|
|
191
200
|
* - pepper: optional server secret
|
|
192
201
|
* - concurrency: max simultaneous operations (default 4)
|
|
193
202
|
* @returns Array of compact hash strings in the same order as input.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
*
|
|
206
|
+
* const listPasswords = ["password1", "password2", "password3"];
|
|
207
|
+
* const passwordsHasherList = await hashBatch(listPasswords, 16);
|
|
194
208
|
*/
|
|
195
|
-
const hashBatch = async (passwords,
|
|
196
|
-
|
|
197
|
-
const params = options?.params ?? DEFAULT_PARAMS;
|
|
198
|
-
const pepper = options?.pepper;
|
|
199
|
-
const concurrency = options?.concurrency ?? 4;
|
|
200
|
-
return mapLimit(passwords, concurrency, p => (0, exports.hash)(p, saltLength, params, pepper));
|
|
209
|
+
const hashBatch = async (passwords, saltLength = 16, concurrency = 4, params = DEFAULT_PARAMS, pepper) => {
|
|
210
|
+
return mapLimit(passwords, concurrency, password => (0, exports.hash)(password, saltLength, params, pepper));
|
|
201
211
|
};
|
|
202
212
|
exports.hashBatch = hashBatch;
|
|
203
213
|
/**
|
|
@@ -211,10 +221,11 @@ exports.hashBatch = hashBatch;
|
|
|
211
221
|
* - pepper: optional server secret
|
|
212
222
|
* - concurrency: max simultaneous operations (default 8)
|
|
213
223
|
* @returns Array of booleans in the same order as input.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* const verifyPasswords = await verifyBatch([{ password: "test", hash: "testHash" }, { password: "test2", hash: "testHash2" }]);
|
|
214
227
|
*/
|
|
215
|
-
const verifyBatch = async (credentials,
|
|
216
|
-
|
|
217
|
-
const concurrency = options?.concurrency ?? 8;
|
|
218
|
-
return mapLimit(credentials, concurrency, c => (0, exports.verify)(c.password, c.hash, pepper));
|
|
228
|
+
const verifyBatch = async (credentials, concurrency = 8, pepper) => {
|
|
229
|
+
return mapLimit(credentials, concurrency, credential => (0, exports.verify)(credential.password, credential.hash, pepper));
|
|
219
230
|
};
|
|
220
231
|
exports.verifyBatch = verifyBatch;
|
package/dist/crypto/jwt.d.ts
CHANGED
|
@@ -1,12 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the payload (claims set) of a JSON Web Token.
|
|
3
|
+
*
|
|
4
|
+
* A JWT payload may contain arbitrary custom claims.
|
|
5
|
+
* Common registered claims:
|
|
6
|
+
* - `exp` (Expiration Time) – Unix timestamp (seconds)
|
|
7
|
+
* - `iat` (Issued At) – Unix timestamp (seconds)
|
|
8
|
+
*
|
|
9
|
+
* Additional claims may be added depending on the application.
|
|
10
|
+
*/
|
|
1
11
|
interface JWTPayload {
|
|
2
12
|
[key: string]: any;
|
|
3
13
|
exp?: number;
|
|
4
14
|
iat?: number;
|
|
5
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Represents the header section of a JWT.
|
|
18
|
+
*
|
|
19
|
+
* - `alg` – Signing algorithm used
|
|
20
|
+
* - `typ` – Token type (typically `"JWT"`)
|
|
21
|
+
*/
|
|
6
22
|
interface JWTHeader {
|
|
7
23
|
alg: string;
|
|
8
24
|
typ: string;
|
|
9
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Supported JWT signing algorithms.
|
|
28
|
+
*
|
|
29
|
+
* - HS* → HMAC (symmetric secret)
|
|
30
|
+
* - RS* → RSA (asymmetric public/private key)
|
|
31
|
+
*/
|
|
32
|
+
type Algorithm = "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512";
|
|
33
|
+
/**
|
|
34
|
+
* Options used when creating a JWT.
|
|
35
|
+
*/
|
|
36
|
+
interface CreateJWTOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Signing algorithm.
|
|
39
|
+
* Defaults to `"HS256"` if not provided.
|
|
40
|
+
*/
|
|
41
|
+
algorithm?: Algorithm;
|
|
42
|
+
/**
|
|
43
|
+
* Token expiration:
|
|
44
|
+
* - Number → seconds
|
|
45
|
+
* - String → time expression (e.g. `"1h"`, `"30m"`, `"2d"`)
|
|
46
|
+
*/
|
|
47
|
+
expiresIn?: number | string;
|
|
48
|
+
}
|
|
10
49
|
/**
|
|
11
50
|
* Creates a JSON Web Token (JWT) signed using HMAC-SHA256 (HS256).
|
|
12
51
|
*
|
|
@@ -19,18 +58,24 @@ interface JWTHeader {
|
|
|
19
58
|
* @param secret - Secret key used to sign the token.
|
|
20
59
|
* @param expiresIn - Optional expiration time in seconds.
|
|
21
60
|
* @returns Signed JWT string.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const jwt = createJWT({ sub: "123" }, "secret", { algorithm: "HS512", expiresIn: "1h" });
|
|
22
64
|
*/
|
|
23
|
-
export declare const createJWT: (payload: JWTPayload, secret: string,
|
|
65
|
+
export declare const createJWT: (payload: JWTPayload, secret: string | Buffer, opts?: number | string | CreateJWTOptions) => string;
|
|
24
66
|
/**
|
|
25
|
-
* Verifies the
|
|
67
|
+
* Verifies the signature and validity of a JWT.
|
|
26
68
|
*
|
|
27
|
-
*
|
|
69
|
+
* Returns `null` if validation fails at any step.
|
|
28
70
|
*
|
|
29
|
-
* @param token - JWT string
|
|
30
|
-
* @param secret - Secret
|
|
31
|
-
* @returns Decoded payload if
|
|
71
|
+
* @param token - JWT string.
|
|
72
|
+
* @param secret - Secret (HMAC) or public key (RSA).
|
|
73
|
+
* @returns Decoded payload if valid, otherwise `null`.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const verifyToken = verifyJWT("token", "secret-key");
|
|
32
77
|
*/
|
|
33
|
-
export declare const verifyJWT: (token: string, secret: string) => JWTPayload | null;
|
|
78
|
+
export declare const verifyJWT: (token: string, secret: string | Buffer) => JWTPayload | null;
|
|
34
79
|
/**
|
|
35
80
|
* Decodes a JWT without verifying its signature.
|
|
36
81
|
*
|
|
@@ -40,6 +85,9 @@ export declare const verifyJWT: (token: string, secret: string) => JWTPayload |
|
|
|
40
85
|
*
|
|
41
86
|
* @param token - JWT string to decode.
|
|
42
87
|
* @returns Object containing decoded header and payload, or `null` if malformed.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* conste decodeToken = decodeJWT("token");
|
|
43
91
|
*/
|
|
44
92
|
export declare const decodeJWT: (token: string) => {
|
|
45
93
|
header: JWTHeader;
|
package/dist/crypto/jwt.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.decodeJWT = exports.verifyJWT = exports.createJWT = void 0;
|
|
4
|
+
const baseException_1 = require("../exceptions/baseException");
|
|
4
5
|
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
class JWTGeneratorException extends baseException_1.BaseException {
|
|
7
|
+
constructor(algorithm) {
|
|
8
|
+
super(`Unsupported algorithm: ${algorithm}`, "JWT_GENERATOR_EXCEPTION");
|
|
9
|
+
this.name = "JWTGeneratorException";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
5
12
|
/**
|
|
6
13
|
* Encodes a string using Base64URL encoding (RFC 7515).
|
|
7
14
|
*
|
|
@@ -33,6 +40,31 @@ function base64UrlDecode(str) {
|
|
|
33
40
|
}
|
|
34
41
|
return Buffer.from(base64, "base64").toString("utf-8");
|
|
35
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Converts a Base64 string into Base64URL format.
|
|
45
|
+
*
|
|
46
|
+
* Used after cryptographic signing operations that return standard Base64.
|
|
47
|
+
*
|
|
48
|
+
* @param b64 - Standard Base64 string.
|
|
49
|
+
* @returns Base64URL-compatible string.
|
|
50
|
+
*/
|
|
51
|
+
function base64ToBase64Url(b64) {
|
|
52
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Converts a Base64URL string into a Buffer.
|
|
56
|
+
*
|
|
57
|
+
* Restores Base64 padding before decoding.
|
|
58
|
+
*
|
|
59
|
+
* @param str - Standard Base64 string.
|
|
60
|
+
* @returns Buffer data
|
|
61
|
+
*/
|
|
62
|
+
function base64UrlToBuffer(str) {
|
|
63
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
64
|
+
while (base64.length % 4)
|
|
65
|
+
base64 += "=";
|
|
66
|
+
return Buffer.from(base64, "base64");
|
|
67
|
+
}
|
|
36
68
|
/**
|
|
37
69
|
* Creates a JSON Web Token (JWT) signed using HMAC-SHA256 (HS256).
|
|
38
70
|
*
|
|
@@ -45,10 +77,17 @@ function base64UrlDecode(str) {
|
|
|
45
77
|
* @param secret - Secret key used to sign the token.
|
|
46
78
|
* @param expiresIn - Optional expiration time in seconds.
|
|
47
79
|
* @returns Signed JWT string.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* const jwt = createJWT({ sub: "123" }, "secret", { algorithm: "HS512", expiresIn: "1h" });
|
|
48
83
|
*/
|
|
49
|
-
const createJWT = (payload, secret,
|
|
84
|
+
const createJWT = (payload, secret, opts = {}) => {
|
|
85
|
+
const options = typeof opts === "object" && opts !== null
|
|
86
|
+
? opts
|
|
87
|
+
: { expiresIn: opts };
|
|
88
|
+
const algorithm = options.algorithm ?? "HS256";
|
|
50
89
|
const header = {
|
|
51
|
-
alg:
|
|
90
|
+
alg: algorithm,
|
|
52
91
|
typ: "JWT",
|
|
53
92
|
};
|
|
54
93
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -56,28 +95,29 @@ const createJWT = (payload, secret, expiresIn) => {
|
|
|
56
95
|
...payload,
|
|
57
96
|
iat: now,
|
|
58
97
|
};
|
|
59
|
-
if (expiresIn)
|
|
60
|
-
|
|
98
|
+
if (options.expiresIn !== undefined) {
|
|
99
|
+
const secs = parseExpiresIn(options.expiresIn);
|
|
100
|
+
if (secs)
|
|
101
|
+
tokenPayload.exp = now + secs;
|
|
102
|
+
}
|
|
61
103
|
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
62
104
|
const encodedPayload = base64UrlEncode(JSON.stringify(tokenPayload));
|
|
63
105
|
const data = `${encodedHeader}.${encodedPayload}`;
|
|
64
|
-
const signature = (
|
|
65
|
-
.update(data)
|
|
66
|
-
.digest("base64")
|
|
67
|
-
.replace(/\+/g, "-")
|
|
68
|
-
.replace(/\//g, "_")
|
|
69
|
-
.replace(/=/g, "");
|
|
106
|
+
const signature = signData(algorithm, data, secret);
|
|
70
107
|
return `${data}.${signature}`;
|
|
71
108
|
};
|
|
72
109
|
exports.createJWT = createJWT;
|
|
73
110
|
/**
|
|
74
|
-
* Verifies the
|
|
111
|
+
* Verifies the signature and validity of a JWT.
|
|
75
112
|
*
|
|
76
|
-
*
|
|
113
|
+
* Returns `null` if validation fails at any step.
|
|
77
114
|
*
|
|
78
|
-
* @param token - JWT string
|
|
79
|
-
* @param secret - Secret
|
|
80
|
-
* @returns Decoded payload if
|
|
115
|
+
* @param token - JWT string.
|
|
116
|
+
* @param secret - Secret (HMAC) or public key (RSA).
|
|
117
|
+
* @returns Decoded payload if valid, otherwise `null`.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const verifyToken = verifyJWT("token", "secret-key");
|
|
81
121
|
*/
|
|
82
122
|
const verifyJWT = (token, secret) => {
|
|
83
123
|
try {
|
|
@@ -85,18 +125,11 @@ const verifyJWT = (token, secret) => {
|
|
|
85
125
|
if (parts.length !== 3)
|
|
86
126
|
return null;
|
|
87
127
|
const [encodedHeader, encodedPayload, signature] = parts;
|
|
128
|
+
const header = JSON.parse(base64UrlDecode(encodedHeader));
|
|
129
|
+
const alg = header.alg;
|
|
88
130
|
const data = `${encodedHeader}.${encodedPayload}`;
|
|
89
|
-
const
|
|
90
|
-
|
|
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))
|
|
131
|
+
const validJWT = verifyData(alg, data, secret, signature);
|
|
132
|
+
if (!validJWT)
|
|
100
133
|
return null;
|
|
101
134
|
const payload = JSON.parse(base64UrlDecode(encodedPayload));
|
|
102
135
|
if (!payload.exp)
|
|
@@ -120,6 +153,9 @@ exports.verifyJWT = verifyJWT;
|
|
|
120
153
|
*
|
|
121
154
|
* @param token - JWT string to decode.
|
|
122
155
|
* @returns Object containing decoded header and payload, or `null` if malformed.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* conste decodeToken = decodeJWT("token");
|
|
123
159
|
*/
|
|
124
160
|
const decodeJWT = (token) => {
|
|
125
161
|
try {
|
|
@@ -136,3 +172,118 @@ const decodeJWT = (token) => {
|
|
|
136
172
|
}
|
|
137
173
|
};
|
|
138
174
|
exports.decodeJWT = decodeJWT;
|
|
175
|
+
/**
|
|
176
|
+
* Parses an `expiresIn` value.
|
|
177
|
+
*
|
|
178
|
+
* Accepts:
|
|
179
|
+
* - Number → seconds
|
|
180
|
+
* - String format:
|
|
181
|
+
* - "30s"
|
|
182
|
+
* - "15m"
|
|
183
|
+
* - "2h"
|
|
184
|
+
* - "7d"
|
|
185
|
+
* - "1y"
|
|
186
|
+
*
|
|
187
|
+
* Returns the duration in seconds.
|
|
188
|
+
*
|
|
189
|
+
* @param input - Expiration value.
|
|
190
|
+
* @returns Seconds or undefined if invalid.
|
|
191
|
+
*/
|
|
192
|
+
function parseExpiresIn(input) {
|
|
193
|
+
if (input === undefined)
|
|
194
|
+
return undefined;
|
|
195
|
+
if (typeof input === "number")
|
|
196
|
+
return input;
|
|
197
|
+
const match = `${input}`.trim().match(/^(\d+)\s*(s|m|h|d|y)?$/i);
|
|
198
|
+
if (!match)
|
|
199
|
+
return undefined;
|
|
200
|
+
const value = parseInt(match[1], 10);
|
|
201
|
+
const unit = (match[2] || "s").toLowerCase();
|
|
202
|
+
switch (unit) {
|
|
203
|
+
case "s":
|
|
204
|
+
return value;
|
|
205
|
+
case "m":
|
|
206
|
+
return value * 60;
|
|
207
|
+
case "h":
|
|
208
|
+
return value * 60 * 60;
|
|
209
|
+
case "d":
|
|
210
|
+
return value * 60 * 60 * 24;
|
|
211
|
+
case "y":
|
|
212
|
+
return value * 60 * 60 * 24 * 365;
|
|
213
|
+
default:
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const hmacAlgorithm = (algorithm) => {
|
|
218
|
+
const verifyAlg = algorithm === "HS256"
|
|
219
|
+
? "sha256"
|
|
220
|
+
: algorithm === "HS384"
|
|
221
|
+
? "sha384"
|
|
222
|
+
: "sha512";
|
|
223
|
+
return verifyAlg;
|
|
224
|
+
};
|
|
225
|
+
const rsaAlgorithm = (algorithm) => {
|
|
226
|
+
const verifyAlg = algorithm === "RS256"
|
|
227
|
+
? "RSA-SHA256"
|
|
228
|
+
: algorithm === "RS384"
|
|
229
|
+
? "RSA-SHA384"
|
|
230
|
+
: "RSA-SHA512";
|
|
231
|
+
return verifyAlg;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Signs JWT data using the specified algorithm.
|
|
235
|
+
*
|
|
236
|
+
* - HS* → HMAC (shared secret)
|
|
237
|
+
* - RS* → RSA (private key)
|
|
238
|
+
*
|
|
239
|
+
* @param algorithm - JWT signing algorithm.
|
|
240
|
+
* @param data - Header + payload.
|
|
241
|
+
* @param key - Secret or private key.
|
|
242
|
+
* @returns Base64URL signature string.
|
|
243
|
+
*/
|
|
244
|
+
function signData(algorithm, data, key) {
|
|
245
|
+
if (algorithm.startsWith("HS")) {
|
|
246
|
+
const b64 = (0, node_crypto_1.createHmac)(hmacAlgorithm(algorithm), key)
|
|
247
|
+
.update(data)
|
|
248
|
+
.digest("base64");
|
|
249
|
+
return base64ToBase64Url(b64);
|
|
250
|
+
}
|
|
251
|
+
if (algorithm.startsWith("RS")) {
|
|
252
|
+
const b64 = (0, node_crypto_1.createSign)(rsaAlgorithm(algorithm))
|
|
253
|
+
.update(data)
|
|
254
|
+
.sign(key, "base64");
|
|
255
|
+
return base64ToBase64Url(b64);
|
|
256
|
+
}
|
|
257
|
+
throw new JWTGeneratorException(algorithm);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Verifies JWT signature using constant-time comparison (HMAC)
|
|
261
|
+
* or RSA verification.
|
|
262
|
+
*
|
|
263
|
+
* - HS* → Uses `timingSafeEqual` to prevent timing attacks.
|
|
264
|
+
* - RS* → Uses Node.js `verify`.
|
|
265
|
+
*
|
|
266
|
+
* @param algorithm - JWT algorithm.
|
|
267
|
+
* @param data - Signed data.
|
|
268
|
+
* @param key - Secret or public key.
|
|
269
|
+
* @param signature - Signature (Base64URL).
|
|
270
|
+
* @returns `true` if signature is valid.
|
|
271
|
+
*/
|
|
272
|
+
function verifyData(algorithm, data, key, signature) {
|
|
273
|
+
if (algorithm.startsWith("HS")) {
|
|
274
|
+
const expectedB64 = (0, node_crypto_1.createHmac)(hmacAlgorithm(algorithm), key)
|
|
275
|
+
.update(data)
|
|
276
|
+
.digest("base64");
|
|
277
|
+
const expectedBuf = base64UrlToBuffer(base64ToBase64Url(expectedB64));
|
|
278
|
+
const sigBuf = base64UrlToBuffer(signature);
|
|
279
|
+
if (expectedBuf.length !== sigBuf.length)
|
|
280
|
+
return false;
|
|
281
|
+
return (0, node_crypto_1.timingSafeEqual)(expectedBuf, sigBuf);
|
|
282
|
+
}
|
|
283
|
+
if (algorithm.startsWith("RS")) {
|
|
284
|
+
const verifier = (0, node_crypto_1.createVerify)(rsaAlgorithm(algorithm)).update(data);
|
|
285
|
+
const sigBuf = base64UrlToBuffer(signature);
|
|
286
|
+
return verifier.verify(key, sigBuf);
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
}
|