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.
Files changed (52) hide show
  1. package/README.md +30 -30
  2. package/dist/app.d.ts +9 -4
  3. package/dist/app.js +9 -7
  4. package/dist/crypto/hasher.d.ts +19 -10
  5. package/dist/crypto/hasher.js +21 -10
  6. package/dist/crypto/jwt.d.ts +55 -7
  7. package/dist/crypto/jwt.js +177 -26
  8. package/dist/http/logger.d.ts +47 -0
  9. package/dist/http/logger.js +47 -0
  10. package/dist/http/nodeNativeHttp.d.ts +4 -0
  11. package/dist/http/nodeNativeHttp.js +12 -4
  12. package/dist/http/request.d.ts +9 -23
  13. package/dist/http/request.js +3 -24
  14. package/dist/http/response.d.ts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/middlewares/cors.d.ts +13 -9
  18. package/dist/middlewares/cors.js +25 -15
  19. package/dist/middlewares/index.d.ts +0 -1
  20. package/dist/middlewares/index.js +1 -3
  21. package/dist/middlewares/session.d.ts +19 -9
  22. package/dist/middlewares/session.js +19 -9
  23. package/dist/parsers/contentParserManager.d.ts +0 -6
  24. package/dist/parsers/contentParserManager.js +0 -6
  25. package/dist/parsers/multipartParser.d.ts +85 -5
  26. package/dist/parsers/multipartParser.js +85 -5
  27. package/dist/parsers/xmlParser.d.ts +90 -0
  28. package/dist/parsers/xmlParser.js +90 -0
  29. package/dist/routing/layer.d.ts +0 -22
  30. package/dist/routing/layer.js +1 -26
  31. package/dist/routing/router.d.ts +0 -36
  32. package/dist/routing/router.js +0 -36
  33. package/dist/routing/routerGroup.d.ts +0 -15
  34. package/dist/routing/routerGroup.js +0 -15
  35. package/dist/sessions/cookies.d.ts +0 -29
  36. package/dist/sessions/cookies.js +0 -19
  37. package/dist/static/contentDisposition.d.ts +78 -19
  38. package/dist/static/contentDisposition.js +78 -19
  39. package/dist/static/fileDownload.d.ts +0 -14
  40. package/dist/static/fileDownload.js +0 -14
  41. package/dist/static/fileStaticHandler.d.ts +0 -11
  42. package/dist/static/fileStaticHandler.js +0 -11
  43. package/dist/storage/storage.js +1 -1
  44. package/dist/storage/uploader.d.ts +21 -0
  45. package/dist/storage/uploader.js +22 -1
  46. package/dist/validators/validationSchema.d.ts +16 -6
  47. package/dist/validators/validationSchema.js +24 -7
  48. package/dist/validators/validator.d.ts +1 -24
  49. package/dist/validators/validator.js +1 -24
  50. package/package.json +2 -2
  51. package/dist/middlewares/auth.d.ts +0 -34
  52. 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(3000, () => {
63
- console.log(`Server running in port: http://localhost:${3000}`);
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: ["GET", "POST"],
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 schemas, which are created as follows:
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
- app.post("/users", (request: Request) => {
182
- const validatedData = request.validateData(userSchema);
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
- return Response.json({
185
- success: true,
186
- data: validatedData,
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
- { sub: user.id, role: user.role },
318
- "secret-key",
319
- 3600,
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);
@@ -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[], options?: {
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
- }>, options?: {
88
- pepper?: string;
89
- concurrency?: number;
90
- }) => Promise<boolean[]>;
99
+ }>, concurrency?: number, pepper?: string) => Promise<boolean[]>;
91
100
  export {};
@@ -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, options) => {
196
- const saltLength = options?.saltLength ?? 16;
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, options) => {
216
- const pepper = options?.pepper;
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;
@@ -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, expiresIn?: number) => string;
65
+ export declare const createJWT: (payload: JWTPayload, secret: string | Buffer, opts?: number | string | CreateJWTOptions) => string;
24
66
  /**
25
- * Verifies the integrity and validity of a JSON Web Token.
67
+ * Verifies the signature and validity of a JWT.
26
68
  *
27
- * If any validation step fails, the function returns `null`.
69
+ * Returns `null` if validation fails at any step.
28
70
  *
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`.
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;
@@ -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, expiresIn) => {
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: "HS256",
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
- tokenPayload.exp = now + expiresIn;
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 = (0, node_crypto_1.createHmac)("sha256", secret)
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 integrity and validity of a JSON Web Token.
111
+ * Verifies the signature and validity of a JWT.
75
112
  *
76
- * If any validation step fails, the function returns `null`.
113
+ * Returns `null` if validation fails at any step.
77
114
  *
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`.
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 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))
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
+ }