@dwtechs/toker 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 DWTechs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,315 @@
1
+
2
+ [![License: MIT](https://img.shields.io/npm/l/@dwtechs/toker.svg?color=brightgreen)](https://opensource.org/licenses/MIT)
3
+ [![npm version](https://badge.fury.io/js/%40dwtechs%2Ftoker.svg)](https://www.npmjs.com/package/@dwtechs/toker)
4
+ [![last version release date](https://img.shields.io/github/release-date/DWTechs/Toker.js)](https://www.npmjs.com/package/@dwtechs/toker)
5
+ ![Jest:coverage](https://img.shields.io/badge/Jest:coverage-100%25-brightgreen.svg)
6
+
7
+ - [Synopsis](#synopsis)
8
+ - [Support](#support)
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [API Reference](#api-reference)
12
+ - [Error Handling](#error-handling)
13
+ - [options](#options)
14
+ - [Express.js](#expressjs)
15
+ - [Contributors](#contributors)
16
+ - [Stack](#stack)
17
+
18
+
19
+ ## Synopsis
20
+
21
+ **[Toker.js](https://github.com/DWTechs/Toker.js)** is an open source JWT management library for Node.js to sign, verify and parse bearer safely.
22
+
23
+ - ๐Ÿ“ฆ Only 1 dependency to check inputs variables
24
+ - ๐Ÿชถ Very lightweight
25
+ - ๐Ÿงช Thoroughly tested
26
+ - ๐Ÿšš Shipped as EcmaScrypt module
27
+ - ๐Ÿ“ Written in Typescript
28
+
29
+
30
+ ## Support
31
+
32
+ - Node.js: 22
33
+
34
+ This is the oldest targeted versions.
35
+ The library uses node:crypto.
36
+
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ $ npm i @dwtechs/toker
42
+ ```
43
+
44
+
45
+ ## Usage
46
+
47
+
48
+ Example of use with Express.js using ES6 module format
49
+
50
+ ```javascript
51
+
52
+ import { compare, randomPwd, encrypt, sign, verify } from "@dwtechs/toker";
53
+
54
+ const { ACCESS_TOKEN_DURATION, REFRESH_TOKEN_DURATION, TOKEN_SECRET } = process.env;
55
+
56
+
57
+ function decodeAccessToken(req, res, next){
58
+ let accessToken: string;
59
+ try {
60
+ accessToken = parseBearer(req.headers.authorization);
61
+ } catch (err: any) {
62
+ return next(err);
63
+ }
64
+ let decodedToken = null;
65
+ try {
66
+ decodedToken = verify(accessToken, [TOKEN_SECRET], true);
67
+ } catch (err: any) {
68
+ return next(err);
69
+ }
70
+ req.body.decodedAccessToken = decodedToken;
71
+ next();
72
+ }
73
+
74
+ function refreshToken(req, res, next) {
75
+ const iss = req.body.decodedAccessToken?.iss;
76
+ const newAccessToken = sign(iss, ACCESS_TOKEN_DURATION, "access", secrets);
77
+ const newRefreshToken = sign(iss, REFRESH_TOKEN_DURATION, "refresh", secrets);
78
+ try {
79
+ res.jwt = sign(req.userId, 3600, "access", [TOKEN_SECRET]);
80
+ next();
81
+ catch(err: any) {
82
+ next(err);
83
+ }
84
+ }
85
+
86
+ export {
87
+ decodeAccessToken,
88
+ refreshToken,
89
+ };
90
+
91
+ ```
92
+
93
+
94
+ ## API Reference
95
+
96
+
97
+ ### Types
98
+ ---
99
+
100
+ ```javascript
101
+
102
+ // JWT
103
+ type Type = "access" | "refresh";
104
+
105
+ type Header = {
106
+ alg: string,
107
+ typ: string,
108
+ kid: number,
109
+ };
110
+
111
+ type Payload = {
112
+ iss: number | string,
113
+ iat: number,
114
+ nbf: number,
115
+ exp: number,
116
+ typ: Type,
117
+ }
118
+
119
+ ```
120
+
121
+ ### Methods
122
+ ---
123
+
124
+ ```javascript
125
+
126
+ // Default values
127
+ const header {
128
+ alg: "HS256", // HMAC using SHA-256 hash algorithm
129
+ typ: "JWT", // JSON Web Token
130
+ kid: 0, // Random key ID
131
+ };
132
+
133
+ /**
134
+ * Signs a JWT (JSON Web Token) with the given parameters.
135
+ *
136
+ * @param {number|string} iss - The issuer of the token, which can be a string or a number.
137
+ * @param {number} duration - The duration for which the token is valid, in seconds.
138
+ * @param {Type} type - The type of the token, either "access" or "refresh".
139
+ * @param {string[]} b64Keys - An array of base64 encoded secrets used for signing the token.
140
+ * @returns {string} The signed JWT as a string.
141
+ * @throws {InvalidIssuerError} Throws when `iss` is not a string or a number - HTTP 400
142
+ * @throws {InvalidSecretsError} Throws when `b64Keys` is not an array or is empty - HTTP 500
143
+ * @throws {InvalidDurationError} Throws when `duration` is not a positive number - HTTP 400
144
+ * @throws {SecretDecodingError} Throws when the secret cannot be decoded from base64 - HTTP 500
145
+ *
146
+ * // Examples that throw specific errors:
147
+ * sign(null, 3600, "access", secrets); // Throws InvalidIssuerError
148
+ * sign("user123", 3600, "access", []); // Throws InvalidSecretsError
149
+ * sign("user123", -1, "access", secrets); // Throws InvalidDurationError
150
+ * sign("user123", 3600, "access", ["invalid-base64!"]); // Throws SecretDecodingError
151
+ * ```
152
+ */
153
+ function sign( iss: number | string,
154
+ duration: number,
155
+ type: Type,
156
+ b64Keys: string[]
157
+ ): string {}
158
+
159
+ /**
160
+ * Verifies a JWT token using the provided base64-encoded secrets.
161
+ *
162
+ * @param {string} token - The JWT token to verify.
163
+ * @param {string[]} b64Keys - An array of base64-encoded secrets used for verification.
164
+ * @param {boolean} ignoreExpiration - Optional flag to ignore the expiration time of the token. Defaults to false.
165
+ * @returns {Payload} The decoded payload of the JWT token.
166
+ * @throws {InvalidTokenError} Throws when the token is malformed, has invalid structure, algorithm, or type - HTTP 401
167
+ * @throws {InvalidSecretsError} Throws when b64Keys is not an array or is empty - HTTP 500
168
+ * @throws {TokenNotActiveError} Throws when the token cannot be used yet (nbf claim) - HTTP 401
169
+ * @throws {TokenExpiredError} Throws when the token has expired (exp claim) - HTTP 401
170
+ * @throws {SecretDecodingError} Throws when the secret is not valid base64 encoded - HTTP 500
171
+ * @throws {InvalidSignatureError} Throws when the token signature is invalid - HTTP 401
172
+ *
173
+ * // Examples that throw specific errors:
174
+ * verify("invalid.token", secrets); // Throws InvalidTokenError
175
+ * verify(validToken, []); // Throws InvalidSecretsError
176
+ * verify(expiredToken, secrets); // Throws TokenExpiredError
177
+ * verify(futureToken, secrets); // Throws TokenNotActiveError
178
+ * verify(tamperedToken, secrets); // Throws InvalidSignatureError
179
+ * verify(validToken, ["invalid-base64!"]); // Throws SecretDecodingError
180
+ * ```
181
+ */
182
+ function verify( token: string,
183
+ b64Keys: string[],
184
+ ignoreExpiration = false
185
+ ): Payload {}
186
+
187
+
188
+ /**
189
+ * Extracts the JWT token from an HTTP Authorization header with Bearer authentication scheme.
190
+ *
191
+ * This function validates that the authorization header follows the correct Bearer token format
192
+ * ("Bearer <token>") and extracts the token portion for further processing.
193
+ *
194
+ * @param {string | undefined} authorization - The Authorization header value from an HTTP request
195
+ * @returns {string} The extracted JWT token as a string
196
+ * @throws {MissingAuthorizationError} Throws when the authorization parameter is undefined - HTTP 401
197
+ * @throws {InvalidBearerFormatError} Throws when the format is invalid - HTTP 401
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * import { parseBearer, MissingAuthorizationError, InvalidBearerFormatError } from "@dwtechs/passken";
202
+ *
203
+ * // Valid Bearer tokens
204
+ * const validHeader = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
205
+ * const token = parseBearer(validHeader);
206
+ * // Returns: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
207
+ *
208
+ * // Handles multiple spaces
209
+ * const headerWithSpaces = "Bearer token123";
210
+ * const token2 = parseBearer(headerWithSpaces);
211
+ * // Returns: "token123"
212
+ *
213
+ * // Examples that throw specific errors:
214
+ * parseBearer(undefined); // Throws MissingAuthorizationError: "Authorization header is missing"
215
+ * parseBearer(""); // Throws InvalidBearerFormatError: "Authorization header must be in the format 'Bearer <token>'"
216
+ * parseBearer("Basic dXNlcjpwYXNz"); // Throws InvalidBearerFormatError
217
+ * parseBearer("Bearer"); // Throws InvalidBearerFormatError
218
+ * parseBearer("Bearer "); // Throws InvalidBearerFormatError
219
+ * ```
220
+ *
221
+ */
222
+ function parseBearer(authorization: string | undefined): string {}
223
+
224
+ ```
225
+
226
+ ## Error Handling
227
+
228
+ Toker uses a structured error system that helps you identify and handle specific error cases. All errors extend from a base `TokerError` class.
229
+
230
+ ### Error Classes Hierarchy
231
+
232
+ ```
233
+ TokerError (abstract base class)
234
+ โ”œโ”€โ”€ MissingAuthorizationError
235
+ โ”œโ”€โ”€ InvalidBearerFormatError
236
+ โ”œโ”€โ”€ InvalidTokenError
237
+ โ”œโ”€โ”€ ExpiredTokenError
238
+ โ”œโ”€โ”€ InactiveTokenError
239
+ โ”œโ”€โ”€ InvalidSignatureError
240
+ โ”œโ”€โ”€ InvalidIssuerError
241
+ โ”œโ”€โ”€ InvalidSecretsError
242
+ โ”œโ”€โ”€ InvalidDurationError
243
+ โ”œโ”€โ”€ InvalidBase64Secret
244
+ ```
245
+
246
+ ### Common Properties
247
+
248
+ All error classes share these properties:
249
+
250
+ - `message`: Human-readable error description
251
+ - `code`: Human-readable error code (e.g., "TOKEN_EXPIRED")
252
+ - `statusCode`: Suggested HTTP status code (e.g., 401)
253
+ - `stack`: Error stack trace
254
+
255
+ ### Using Error Handling
256
+
257
+ ```typescript
258
+ import { sign, verify, parseBearer, TokenExpiredError, InvalidSignatureError } from "@dwtechs/passken";
259
+
260
+ try {
261
+ // Attempt to verify a token
262
+ const payload = verify(token, secrets);
263
+ // Token is valid, proceed with the payload
264
+ } catch (error) {
265
+ if (error instanceof TokenExpiredError) {
266
+ // Handle expired token (e.g., prompt for reauthentication)
267
+ console.log('Your session has expired. Please log in again.');
268
+ console.log(`Status code: ${error.statusCode}`); // 401
269
+ } else if (error instanceof InvalidSignatureError) {
270
+ // Handle tampered token
271
+ console.log('Invalid token signature detected');
272
+ console.log(`Status code: ${error.statusCode}`); // 401
273
+ } else {
274
+ // Handle other verification errors
275
+ console.log(`Token verification failed: ${error.message}`);
276
+ }
277
+ }
278
+ ```
279
+
280
+ ### Error Types and HTTP Status Codes
281
+
282
+ | Error Class | Code | Status Code | Description |
283
+ |-------------|------|-------------|-------------|
284
+ | MissingAuthorizationError | MISSING_AUTHORIZATION | 401 | Authorization header is missing |
285
+ | InvalidBearerFormatError | INVALID_BEARER_FORMAT | 401 | Authorization header must be in the format 'Bearer <token>' |
286
+ | InvalidTokenError | INVALID_TOKEN | 401 | Invalid or malformed JWT token |
287
+ | ExpiredTokenError | EXPIRED_TOKEN | 401 | JWT token has expired |
288
+ | InactiveTokenError | INACTIVE_TOKEN | 401 | JWT token cannot be used yet (nbf claim) |
289
+ | InvalidSignatureError | INVALID_SIGNATURE | 401 | JWT token signature is invalid |
290
+ | InvalidIssuerError | INVALID_ISSUER | 400 | iss must be a string or a number |
291
+ | InvalidSecretsError | INVALID_SECRETS | 500 | b64Keys must be an array |
292
+ | InvalidDurationError | INVALID_DURATION | 400 | duration must be a positive number |
293
+ | InvalidBase64Secret | INVALID_BASE64_SECRET | 500 | could not decode the base64 secret |
294
+
295
+ ## Express.js
296
+
297
+ You can use Toker directly as Express.js middlewares using [@dwtechs/toker-express library](https://www.npmjs.com/package/@dwtechs/toker-express).
298
+ This way you do not have to write express controllers yourself to use **Toker**.
299
+
300
+
301
+ ## Contributors
302
+
303
+ **Toker.js** is still in development and we would be glad to get all the help you can provide.
304
+ To contribute please read **[contributor.md](https://github.com/DWTechs/Toker.js/blob/main/contributor.md)** for detailed installation guide.
305
+
306
+
307
+ ## Stack
308
+
309
+ | Purpose | Choice | Motivation |
310
+ | :-------------- | :------------------------------------------: | -------------------------------------------------------------: |
311
+ | repository | [Github](https://github.com/) | hosting for software development version control using Git |
312
+ | package manager | [npm](https://www.npmjs.com/get-npm) | default node.js package manager |
313
+ | language | [TypeScript](https://www.typescriptlang.org) | static type checking along with the latest ECMAScript features |
314
+ | module bundler | [Rollup.js](https://rollupjs.org) | advanced module bundler for ES6 modules |
315
+ | unit testing | [Jest](https://jestjs.io/) | delightful testing with a focus on simplicity |
@@ -0,0 +1,111 @@
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2022 DWTechs
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ https://github.com/DWTechs/Toker.js
25
+ */
26
+
27
+
28
+ export type Type = "access" | "refresh";
29
+ export type Header = {
30
+ alg: string;
31
+ typ: string;
32
+ kid: number;
33
+ };
34
+ export type Payload = {
35
+ iss: number | string;
36
+ iat: number;
37
+ nbf: number;
38
+ exp: number;
39
+ typ: Type;
40
+ };
41
+
42
+ // Error classes
43
+ export abstract class TokerError extends Error {
44
+ abstract readonly code: string;
45
+ abstract readonly statusCode: number;
46
+ }
47
+
48
+ export class MissingAuthorizationError extends TokerError {
49
+ readonly code: string;
50
+ readonly statusCode: number;
51
+ }
52
+
53
+ export class InvalidBearerFormatError extends TokerError {
54
+ readonly code: string;
55
+ readonly statusCode: number;
56
+ }
57
+
58
+ export class InvalidTokenError extends TokerError {
59
+ readonly code: string;
60
+ readonly statusCode: number;
61
+ }
62
+
63
+ export class ExpiredTokenError extends TokerError {
64
+ readonly code: string;
65
+ readonly statusCode: number;
66
+ }
67
+
68
+ export class InactiveTokenError extends TokerError {
69
+ readonly code: string;
70
+ readonly statusCode: number;
71
+ }
72
+
73
+ export class InvalidSignatureError extends TokerError {
74
+ readonly code: string;
75
+ readonly statusCode: number;
76
+ }
77
+
78
+ // export class MissingClaimsError extends TokerError {
79
+ // readonly code: string;
80
+ // readonly statusCode: number;
81
+ // }
82
+
83
+ export class InvalidIssuerError extends TokerError {
84
+ readonly code: string;
85
+ readonly statusCode: number;
86
+ }
87
+
88
+ export class InvalidSecretsError extends TokerError {
89
+ readonly code: string;
90
+ readonly statusCode: number;
91
+ }
92
+
93
+ export class InvalidDurationError extends TokerError {
94
+ readonly code: string;
95
+ readonly statusCode: number;
96
+ }
97
+
98
+ export class InvalidBase64Secret extends TokerError {
99
+ readonly code: string;
100
+ readonly statusCode: number;
101
+ }
102
+
103
+ declare function sign(iss: number | string, duration: number, type: Type, b64Keys: string[]): string;
104
+ declare function verify(token: string, b64Keys: string[], ignoreExpiration?: boolean): Payload;
105
+ declare function parseBearer(authorization: string | undefined): string;
106
+
107
+ export {
108
+ sign,
109
+ verify,
110
+ parseBearer,
111
+ };
package/dist/toker.js ADDED
@@ -0,0 +1,190 @@
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2022 DWTechs
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ https://github.com/DWTechs/Toker.js
25
+ */
26
+
27
+ import { isString, isNumber, isArray, isPositive, isJson, isBase64 } from '@dwtechs/checkard';
28
+ import { b64Decode, b64Encode, hash, tse } from '@dwtechs/hashitaka';
29
+ import { Buffer } from 'buffer';
30
+
31
+ const TOKER_PREFIX = "Toker: ";
32
+ class TokerError extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = this.constructor.name;
36
+ if (Error.captureStackTrace) {
37
+ Error.captureStackTrace(this, this.constructor);
38
+ }
39
+ }
40
+ }
41
+ class MissingAuthorizationError extends TokerError {
42
+ constructor(message = `${TOKER_PREFIX}Authorization header is missing`) {
43
+ super(message);
44
+ this.code = "MISSING_AUTHORIZATION";
45
+ this.statusCode = 401;
46
+ }
47
+ }
48
+ class InvalidBearerFormatError extends TokerError {
49
+ constructor(message = `${TOKER_PREFIX}Authorization header must be in the format 'Bearer <token>'`) {
50
+ super(message);
51
+ this.code = "INVALID_BEARER_FORMAT";
52
+ this.statusCode = 401;
53
+ }
54
+ }
55
+ class InvalidTokenError extends TokerError {
56
+ constructor(message = `${TOKER_PREFIX}Invalid or malformed JWT token`) {
57
+ super(message);
58
+ this.code = "INVALID_TOKEN";
59
+ this.statusCode = 401;
60
+ }
61
+ }
62
+ class ExpiredTokenError extends TokerError {
63
+ constructor(message = `${TOKER_PREFIX}JWT token has expired`) {
64
+ super(message);
65
+ this.code = "EXPIRED_TOKEN";
66
+ this.statusCode = 401;
67
+ }
68
+ }
69
+ class InactiveTokenError extends TokerError {
70
+ constructor(message = `${TOKER_PREFIX}JWT token cannot be used yet (nbf claim)`) {
71
+ super(message);
72
+ this.code = "INACTIVE_TOKEN";
73
+ this.statusCode = 401;
74
+ }
75
+ }
76
+ class InvalidSignatureError extends TokerError {
77
+ constructor(message = `${TOKER_PREFIX}JWT token signature is invalid`) {
78
+ super(message);
79
+ this.code = "INVALID_SIGNATURE";
80
+ this.statusCode = 401;
81
+ }
82
+ }
83
+ class InvalidIssuerError extends TokerError {
84
+ constructor(message = `${TOKER_PREFIX}iss must be a string or a number`) {
85
+ super(message);
86
+ this.code = "INVALID_ISSUER";
87
+ this.statusCode = 400;
88
+ }
89
+ }
90
+ class InvalidSecretsError extends TokerError {
91
+ constructor(message = `${TOKER_PREFIX}b64Keys must be an array`) {
92
+ super(message);
93
+ this.code = "INVALID_SECRETS";
94
+ this.statusCode = 500;
95
+ }
96
+ }
97
+ class InvalidDurationError extends TokerError {
98
+ constructor(message = `${TOKER_PREFIX}duration must be a positive number`) {
99
+ super(message);
100
+ this.code = "INVALID_DURATION";
101
+ this.statusCode = 400;
102
+ }
103
+ }
104
+ class InvalidBase64Secret extends TokerError {
105
+ constructor(message = `${TOKER_PREFIX}could not decode the base64 secret`) {
106
+ super(message);
107
+ this.code = "INVALID_BASE64_SECRET";
108
+ this.statusCode = 500;
109
+ }
110
+ }
111
+
112
+ const header = {
113
+ alg: "HS256",
114
+ typ: "JWT",
115
+ kid: 0,
116
+ };
117
+ function sign(iss, duration, type, b64Keys) {
118
+ if (!isString(iss, "!0") && !isNumber(iss, true))
119
+ throw new InvalidIssuerError();
120
+ if (!isArray(b64Keys, ">", 0))
121
+ throw new InvalidSecretsError();
122
+ if (!isNumber(duration, false) || !isPositive(duration, true))
123
+ throw new InvalidDurationError();
124
+ header.kid = randomPick(b64Keys);
125
+ const b64Secret = b64Keys[header.kid];
126
+ const secret = b64Decode(b64Secret, true);
127
+ if (!secret)
128
+ throw new InvalidBase64Secret();
129
+ const iat = Math.floor(Date.now() / 1000);
130
+ const nbf = iat + 1;
131
+ const exp = duration > 60 ? iat + duration : iat + 60 * 15;
132
+ const typ = type === "refresh" ? type : "access";
133
+ const payload = { iss, iat, nbf, exp, typ };
134
+ const b64Header = b64Encode(JSON.stringify(header));
135
+ const b64Payload = b64Encode(JSON.stringify(payload));
136
+ const b64Signature = hash(`${b64Header}.${b64Payload}`, secret);
137
+ return `${b64Header}.${b64Payload}.${b64Signature}`;
138
+ }
139
+ function verify(token, b64Keys, ignoreExpiration = false) {
140
+ const segments = token.split(".");
141
+ if (segments.length !== 3)
142
+ throw new InvalidTokenError();
143
+ const [b64Header, b64Payload, b64Signature] = segments;
144
+ if (!b64Header || !b64Payload || !b64Signature)
145
+ throw new InvalidTokenError();
146
+ if (!isArray(b64Keys, ">", 0))
147
+ throw new InvalidSecretsError();
148
+ const headerString = b64Decode(b64Header);
149
+ const payloadString = b64Decode(b64Payload);
150
+ if (!isJson(headerString) || !isJson(payloadString))
151
+ throw new InvalidTokenError();
152
+ const header = JSON.parse(headerString);
153
+ const payload = JSON.parse(payloadString);
154
+ if (header.alg !== "HS256")
155
+ throw new InvalidTokenError();
156
+ if (header.typ !== "JWT")
157
+ throw new InvalidTokenError();
158
+ if (!isString(header.kid, "!0") && !isNumber(header.kid, true))
159
+ throw new InvalidTokenError();
160
+ const now = Math.floor(Date.now() / 1000);
161
+ if (payload.nbf && payload.nbf > now)
162
+ throw new InactiveTokenError();
163
+ if (!ignoreExpiration && payload.exp < now)
164
+ throw new ExpiredTokenError();
165
+ const b64Secret = b64Keys[header.kid];
166
+ if (!isBase64(b64Secret, true))
167
+ throw new InvalidBase64Secret();
168
+ const secret = b64Decode(b64Secret);
169
+ const expectedSignature = hash(`${b64Header}.${b64Payload}`, secret);
170
+ const safeA = Buffer.from(expectedSignature);
171
+ const safeB = Buffer.from(b64Signature);
172
+ if (!tse(safeA, safeB))
173
+ throw new InvalidSignatureError();
174
+ return payload;
175
+ }
176
+ function parseBearer(authorization) {
177
+ if (!authorization)
178
+ throw new MissingAuthorizationError();
179
+ if (!authorization.startsWith("Bearer "))
180
+ throw new InvalidBearerFormatError();
181
+ const parts = authorization.split(" ").filter(part => part.length > 0);
182
+ if (parts.length < 2 || !parts[1])
183
+ throw new InvalidBearerFormatError();
184
+ return parts[1];
185
+ }
186
+ function randomPick(array) {
187
+ return Math.floor(Math.random() * array.length);
188
+ }
189
+
190
+ export { ExpiredTokenError, InactiveTokenError, InvalidBase64Secret, InvalidBearerFormatError, InvalidDurationError, InvalidIssuerError, InvalidSecretsError, InvalidSignatureError, InvalidTokenError, MissingAuthorizationError, TokerError, parseBearer, sign, verify };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@dwtechs/toker",
3
+ "version": "0.1.0",
4
+ "description": "Open source JWT management library for Node.js to sign, verify and parse bearer safely.",
5
+ "keywords": [
6
+ "jwt",
7
+ "token",
8
+ "bearer",
9
+ "authentication"
10
+ ],
11
+ "homepage": "https://github.com/DWTechs/Toker.js",
12
+ "main": "dist/toker",
13
+ "types": "dist/toker",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/DWTechs/Toker.js"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/DWTechs/Toker.js/issues",
20
+ "email": ""
21
+ },
22
+ "license": "MIT",
23
+ "author": {
24
+ "name": "Ludovic Cluber",
25
+ "email": "http://www.lcluber.com/contact",
26
+ "url": "http://www.lcluber.com"
27
+ },
28
+ "contributors": [],
29
+ "scripts": {
30
+ "start": "",
31
+ "prebuild": "npm install",
32
+ "build": "node ./scripts/clear && tsc && npm run rollup && node ./scripts/copy && npm run test",
33
+ "rollup": "rollup --config rollup.config.mjs",
34
+ "test": "jest --coverage"
35
+ },
36
+ "files": [
37
+ "dist/"
38
+ ],
39
+ "dependencies": {
40
+ "@dwtechs/checkard": "3.2.3",
41
+ "@dwtechs/hashitaka": "0.2.1"
42
+ },
43
+ "devDependencies": {
44
+ "@babel/preset-env": "7.26.0",
45
+ "@types/node": "24.1.0",
46
+ "jest": "29.7.0",
47
+ "rollup": "4.24.0",
48
+ "typescript": "5.6.3"
49
+ }
50
+ }