@dwtechs/toker 0.1.0 โ†’ 0.1.2

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 CHANGED
@@ -2,7 +2,7 @@
2
2
  [![License: MIT](https://img.shields.io/npm/l/@dwtechs/toker.svg?color=brightgreen)](https://opensource.org/licenses/MIT)
3
3
  [![npm version](https://badge.fury.io/js/%40dwtechs%2Ftoker.svg)](https://www.npmjs.com/package/@dwtechs/toker)
4
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)
5
+ ![Jest:coverage](https://img.shields.io/badge/Jest:coverage-93%25-brightgreen.svg)
6
6
 
7
7
  - [Synopsis](#synopsis)
8
8
  - [Support](#support)
@@ -20,7 +20,7 @@
20
20
 
21
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
22
 
23
- - ๐Ÿ“ฆ Only 1 dependency to check inputs variables
23
+ - ๐Ÿ“ฆ Only 2 dependencies to check inputs variables and secure hash creation and comparison
24
24
  - ๐Ÿชถ Very lightweight
25
25
  - ๐Ÿงช Thoroughly tested
26
26
  - ๐Ÿšš Shipped as EcmaScrypt module
@@ -227,21 +227,6 @@ function parseBearer(authorization: string | undefined): string {}
227
227
 
228
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
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
230
 
246
231
  ### Common Properties
247
232
 
package/dist/toker.d.ts CHANGED
@@ -38,66 +38,60 @@ export type Payload = {
38
38
  exp: number;
39
39
  typ: Type;
40
40
  };
41
-
42
- // Error classes
43
- export abstract class TokerError extends Error {
44
- abstract readonly code: string;
45
- abstract readonly statusCode: number;
41
+ export declare abstract class TokerError extends Error {
42
+ abstract readonly code: string;
43
+ abstract readonly statusCode: number;
44
+ constructor(message: string, causedBy?: Error);
46
45
  }
47
-
48
- export class MissingAuthorizationError extends TokerError {
49
- readonly code: string;
50
- readonly statusCode: number;
46
+ export declare class MissingAuthorizationError extends TokerError {
47
+ readonly code = "MISSING_AUTHORIZATION";
48
+ readonly statusCode = 401;
49
+ constructor();
51
50
  }
52
-
53
- export class InvalidBearerFormatError extends TokerError {
54
- readonly code: string;
55
- readonly statusCode: number;
51
+ export declare class InvalidBearerFormatError extends TokerError {
52
+ readonly code = "INVALID_BEARER_FORMAT";
53
+ readonly statusCode = 401;
54
+ constructor();
56
55
  }
57
-
58
- export class InvalidTokenError extends TokerError {
59
- readonly code: string;
60
- readonly statusCode: number;
56
+ export declare class InvalidTokenError extends TokerError {
57
+ readonly code = "INVALID_TOKEN";
58
+ readonly statusCode = 401;
59
+ constructor(causedBy?: Error);
61
60
  }
62
-
63
- export class ExpiredTokenError extends TokerError {
64
- readonly code: string;
65
- readonly statusCode: number;
61
+ export declare class ExpiredTokenError extends TokerError {
62
+ readonly code = "EXPIRED_TOKEN";
63
+ readonly statusCode = 401;
64
+ constructor();
66
65
  }
67
-
68
- export class InactiveTokenError extends TokerError {
69
- readonly code: string;
70
- readonly statusCode: number;
66
+ export declare class InactiveTokenError extends TokerError {
67
+ readonly code = "INACTIVE_TOKEN";
68
+ readonly statusCode = 401;
69
+ constructor();
71
70
  }
72
-
73
- export class InvalidSignatureError extends TokerError {
74
- readonly code: string;
75
- readonly statusCode: number;
71
+ export declare class InvalidSignatureError extends TokerError {
72
+ readonly code = "INVALID_SIGNATURE";
73
+ readonly statusCode = 401;
74
+ constructor(causedBy?: Error);
76
75
  }
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;
76
+ export declare class InvalidIssuerError extends TokerError {
77
+ readonly code = "INVALID_ISSUER";
78
+ readonly statusCode = 400;
79
+ constructor();
86
80
  }
87
-
88
- export class InvalidSecretsError extends TokerError {
89
- readonly code: string;
90
- readonly statusCode: number;
81
+ export declare class InvalidSecretsError extends TokerError {
82
+ readonly code = "INVALID_SECRETS";
83
+ readonly statusCode = 500;
84
+ constructor(causedBy?: Error);
91
85
  }
92
-
93
- export class InvalidDurationError extends TokerError {
94
- readonly code: string;
95
- readonly statusCode: number;
86
+ export declare class InvalidDurationError extends TokerError {
87
+ readonly code = "INVALID_DURATION";
88
+ readonly statusCode = 400;
89
+ constructor(causedBy?: Error);
96
90
  }
97
-
98
- export class InvalidBase64Secret extends TokerError {
99
- readonly code: string;
100
- readonly statusCode: number;
91
+ export declare class InvalidBase64Secret extends TokerError {
92
+ readonly code = "INVALID_BASE64_SECRET";
93
+ readonly statusCode = 500;
94
+ constructor(causedBy?: Error);
101
95
  }
102
96
 
103
97
  declare function sign(iss: number | string, duration: number, type: Type, b64Keys: string[]): string;
package/dist/toker.js CHANGED
@@ -1,4 +1,4 @@
1
- /*
1
+ /*
2
2
  MIT License
3
3
 
4
4
  Copyright (c) 2022 DWTechs
@@ -20,90 +20,93 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
20
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
21
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  SOFTWARE.
23
-
24
- https://github.com/DWTechs/Toker.js
25
- */
26
-
23
+
24
+ https://github.com/DWTechs/Toker.js
25
+ */
26
+
27
27
  import { isString, isNumber, isArray, isPositive, isJson, isBase64 } from '@dwtechs/checkard';
28
28
  import { b64Decode, b64Encode, hash, tse } from '@dwtechs/hashitaka';
29
29
  import { Buffer } from 'buffer';
30
30
 
31
31
  const TOKER_PREFIX = "Toker: ";
32
+ function chainMessage(message, err) {
33
+ return `${message} - caused by: ${err.message}`;
34
+ }
32
35
  class TokerError extends Error {
33
- constructor(message) {
34
- super(message);
35
- this.name = this.constructor.name;
36
+ constructor(message, causedBy) {
37
+ super(causedBy ? chainMessage(message, causedBy) : message);
38
+ this.name = `${TOKER_PREFIX}${this.constructor.name}`;
36
39
  if (Error.captureStackTrace) {
37
40
  Error.captureStackTrace(this, this.constructor);
38
41
  }
39
42
  }
40
43
  }
41
44
  class MissingAuthorizationError extends TokerError {
42
- constructor(message = `${TOKER_PREFIX}Authorization header is missing`) {
43
- super(message);
45
+ constructor() {
46
+ super(`${TOKER_PREFIX}Authorization header is missing`);
44
47
  this.code = "MISSING_AUTHORIZATION";
45
48
  this.statusCode = 401;
46
49
  }
47
50
  }
48
51
  class InvalidBearerFormatError extends TokerError {
49
- constructor(message = `${TOKER_PREFIX}Authorization header must be in the format 'Bearer <token>'`) {
50
- super(message);
52
+ constructor() {
53
+ super(`${TOKER_PREFIX}Authorization header must be in the format 'Bearer <token>'`);
51
54
  this.code = "INVALID_BEARER_FORMAT";
52
55
  this.statusCode = 401;
53
56
  }
54
57
  }
55
58
  class InvalidTokenError extends TokerError {
56
- constructor(message = `${TOKER_PREFIX}Invalid or malformed JWT token`) {
57
- super(message);
59
+ constructor(causedBy) {
60
+ super(`${TOKER_PREFIX}Invalid or malformed JWT token`, causedBy);
58
61
  this.code = "INVALID_TOKEN";
59
62
  this.statusCode = 401;
60
63
  }
61
64
  }
62
65
  class ExpiredTokenError extends TokerError {
63
- constructor(message = `${TOKER_PREFIX}JWT token has expired`) {
64
- super(message);
66
+ constructor() {
67
+ super(`${TOKER_PREFIX}JWT token has expired`);
65
68
  this.code = "EXPIRED_TOKEN";
66
69
  this.statusCode = 401;
67
70
  }
68
71
  }
69
72
  class InactiveTokenError extends TokerError {
70
- constructor(message = `${TOKER_PREFIX}JWT token cannot be used yet (nbf claim)`) {
71
- super(message);
73
+ constructor() {
74
+ super(`${TOKER_PREFIX}JWT token cannot be used yet (nbf claim)`);
72
75
  this.code = "INACTIVE_TOKEN";
73
76
  this.statusCode = 401;
74
77
  }
75
78
  }
76
79
  class InvalidSignatureError extends TokerError {
77
- constructor(message = `${TOKER_PREFIX}JWT token signature is invalid`) {
78
- super(message);
80
+ constructor(causedBy) {
81
+ super(`${TOKER_PREFIX}JWT token signature is invalid`, causedBy);
79
82
  this.code = "INVALID_SIGNATURE";
80
83
  this.statusCode = 401;
81
84
  }
82
85
  }
83
86
  class InvalidIssuerError extends TokerError {
84
- constructor(message = `${TOKER_PREFIX}iss must be a string or a number`) {
85
- super(message);
87
+ constructor() {
88
+ super(`${TOKER_PREFIX}iss must be a string or a number`);
86
89
  this.code = "INVALID_ISSUER";
87
90
  this.statusCode = 400;
88
91
  }
89
92
  }
90
93
  class InvalidSecretsError extends TokerError {
91
- constructor(message = `${TOKER_PREFIX}b64Keys must be an array`) {
92
- super(message);
94
+ constructor(causedBy) {
95
+ super(`${TOKER_PREFIX}b64Keys must be an array`, causedBy);
93
96
  this.code = "INVALID_SECRETS";
94
97
  this.statusCode = 500;
95
98
  }
96
99
  }
97
100
  class InvalidDurationError extends TokerError {
98
- constructor(message = `${TOKER_PREFIX}duration must be a positive number`) {
99
- super(message);
101
+ constructor(causedBy) {
102
+ super(`${TOKER_PREFIX}duration must be a positive number`, causedBy);
100
103
  this.code = "INVALID_DURATION";
101
104
  this.statusCode = 400;
102
105
  }
103
106
  }
104
107
  class InvalidBase64Secret extends TokerError {
105
- constructor(message = `${TOKER_PREFIX}could not decode the base64 secret`) {
106
- super(message);
108
+ constructor(causedBy) {
109
+ super(`${TOKER_PREFIX}could not decode the base64 secret`, causedBy);
107
110
  this.code = "INVALID_BASE64_SECRET";
108
111
  this.statusCode = 500;
109
112
  }
@@ -117,22 +120,28 @@ const header = {
117
120
  function sign(iss, duration, type, b64Keys) {
118
121
  if (!isString(iss, "!0") && !isNumber(iss, true))
119
122
  throw new InvalidIssuerError();
120
- if (!isArray(b64Keys, ">", 0))
121
- throw new InvalidSecretsError();
122
- if (!isNumber(duration, false) || !isPositive(duration, true))
123
- throw new InvalidDurationError();
123
+ try {
124
+ isArray(b64Keys, ">", 0, true);
125
+ }
126
+ catch (err) {
127
+ throw new InvalidSecretsError(err);
128
+ }
129
+ try {
130
+ isPositive(duration, true, true);
131
+ }
132
+ catch (err) {
133
+ throw new InvalidDurationError(err);
134
+ }
124
135
  header.kid = randomPick(b64Keys);
125
136
  const b64Secret = b64Keys[header.kid];
126
137
  const secret = b64Decode(b64Secret, true);
127
- if (!secret)
128
- throw new InvalidBase64Secret();
129
138
  const iat = Math.floor(Date.now() / 1000);
130
- const nbf = iat + 1;
139
+ const nbf = iat;
131
140
  const exp = duration > 60 ? iat + duration : iat + 60 * 15;
132
141
  const typ = type === "refresh" ? type : "access";
133
142
  const payload = { iss, iat, nbf, exp, typ };
134
- const b64Header = b64Encode(JSON.stringify(header));
135
- const b64Payload = b64Encode(JSON.stringify(payload));
143
+ const b64Header = b64Encode(JSON.stringify(header), true);
144
+ const b64Payload = b64Encode(JSON.stringify(payload), true);
136
145
  const b64Signature = hash(`${b64Header}.${b64Payload}`, secret);
137
146
  return `${b64Header}.${b64Payload}.${b64Signature}`;
138
147
  }
@@ -143,14 +152,25 @@ function verify(token, b64Keys, ignoreExpiration = false) {
143
152
  const [b64Header, b64Payload, b64Signature] = segments;
144
153
  if (!b64Header || !b64Payload || !b64Signature)
145
154
  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);
155
+ try {
156
+ isArray(b64Keys, ">", 0, true);
157
+ }
158
+ catch (err) {
159
+ throw new InvalidSecretsError(err);
160
+ }
161
+ let headerStr;
162
+ let payloadStr;
163
+ try {
164
+ headerStr = b64Decode(b64Header, true);
165
+ payloadStr = b64Decode(b64Payload, true);
166
+ isJson(headerStr, true);
167
+ isJson(payloadStr, true);
168
+ }
169
+ catch (err) {
170
+ throw new InvalidTokenError(err);
171
+ }
172
+ const header = JSON.parse(headerStr);
173
+ const payload = JSON.parse(payloadStr);
154
174
  if (header.alg !== "HS256")
155
175
  throw new InvalidTokenError();
156
176
  if (header.typ !== "JWT")
@@ -163,13 +183,24 @@ function verify(token, b64Keys, ignoreExpiration = false) {
163
183
  if (!ignoreExpiration && payload.exp < now)
164
184
  throw new ExpiredTokenError();
165
185
  const b64Secret = b64Keys[header.kid];
166
- if (!isBase64(b64Secret, true))
167
- throw new InvalidBase64Secret();
168
- const secret = b64Decode(b64Secret);
186
+ try {
187
+ isBase64(b64Secret, true, true);
188
+ }
189
+ catch (err) {
190
+ throw new InvalidBase64Secret(err);
191
+ }
192
+ const secret = b64Decode(b64Secret, true);
169
193
  const expectedSignature = hash(`${b64Header}.${b64Payload}`, secret);
170
194
  const safeA = Buffer.from(expectedSignature);
171
195
  const safeB = Buffer.from(b64Signature);
172
- if (!tse(safeA, safeB))
196
+ let signaturesMatch;
197
+ try {
198
+ signaturesMatch = tse(safeA, safeB);
199
+ }
200
+ catch (err) {
201
+ throw new InvalidSignatureError(err);
202
+ }
203
+ if (!signaturesMatch)
173
204
  throw new InvalidSignatureError();
174
205
  return payload;
175
206
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwtechs/toker",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Open source JWT management library for Node.js to sign, verify and parse bearer safely.",
5
5
  "keywords": [
6
6
  "jwt",
@@ -37,8 +37,8 @@
37
37
  "dist/"
38
38
  ],
39
39
  "dependencies": {
40
- "@dwtechs/checkard": "3.2.3",
41
- "@dwtechs/hashitaka": "0.2.1"
40
+ "@dwtechs/checkard": "3.5.1",
41
+ "@dwtechs/hashitaka": "0.3.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@babel/preset-env": "7.26.0",