@knsdev/node-utils 3.2.0 → 3.3.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/README.md +56 -9
- package/dist/ECApiCodes.d.ts +1 -1
- package/dist/ECApiCodes.d.ts.map +1 -1
- package/dist/TokenUtils.d.ts +70 -0
- package/dist/TokenUtils.d.ts.map +1 -0
- package/dist/TokenUtils.js +108 -0
- package/dist/TokenUtils.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @knsdev/node-utils
|
|
2
2
|
|
|
3
|
-
TypeScript utilities for Node.js APIs: JWT (HS256), bcrypt passwords, Express cookies and small Express helpers, OAuth (Google / Facebook), Cloudflare Turnstile, HMAC, validation helpers, Zod schemas, pagination/sleep helpers, and math/random utilities. Published as **ES modules** (`"type": "module"`).
|
|
3
|
+
TypeScript utilities for Node.js APIs: JWT (HS256), access/refresh token pairs with HMAC hashes (`TokenUtils`), bcrypt passwords, Express cookies and small Express helpers, OAuth (Google / Facebook), Cloudflare Turnstile, HMAC, validation helpers, Zod schemas, pagination/sleep helpers, and math/random utilities. Published as **ES modules** (`"type": "module"`).
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -97,6 +97,53 @@ if (signResult.error) {
|
|
|
97
97
|
}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
### `TokenUtils`
|
|
101
|
+
|
|
102
|
+
Higher-level helpers on top of [`JwtUtils`](#jwtutils) and [`HMACUtils`](#hmacutils): issue an access JWT plus a **64-character Base58** refresh token, derive **HMAC-SHA256 hashes** of both for opaque storage, decode access tokens (tuple or throwing), and validate refresh token shape/length.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { TokenUtils } from "@knsdev/node-utils/TokenUtils.js";
|
|
106
|
+
|
|
107
|
+
type Session = { sub: string; role: string };
|
|
108
|
+
|
|
109
|
+
const issued = TokenUtils.genTokens<Session>(
|
|
110
|
+
process.env.JWT_SECRET!,
|
|
111
|
+
{ sub: "user-1", role: "user" },
|
|
112
|
+
15 * 60 * 1000,
|
|
113
|
+
);
|
|
114
|
+
if (issued.error) {
|
|
115
|
+
/* InternalServerError with JWT__ENCRYPTION_FAILED */
|
|
116
|
+
} else {
|
|
117
|
+
const { accessToken, refreshToken, accessTokenHash, refreshTokenHash } =
|
|
118
|
+
issued.data;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const access = TokenUtils.genAccessToken<Session>(
|
|
122
|
+
process.env.JWT_SECRET!,
|
|
123
|
+
payload,
|
|
124
|
+
3600_000,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const decoded = TokenUtils.decodeAccessToken<Session>(
|
|
128
|
+
process.env.JWT_SECRET!,
|
|
129
|
+
accessToken,
|
|
130
|
+
);
|
|
131
|
+
if (decoded.error) {
|
|
132
|
+
/* InternalServerError with JWT__INVALID_OR_EXPIRED */
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const user = TokenUtils.decodeAccessTokenOrThrow<Session>(
|
|
136
|
+
process.env.JWT_SECRET!,
|
|
137
|
+
accessToken,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const REFRESH_LEN = 64;
|
|
141
|
+
if (TokenUtils.validateRefreshToken(token, REFRESH_LEN)) {
|
|
142
|
+
/* token narrowed to string */
|
|
143
|
+
}
|
|
144
|
+
TokenUtils.validateRefreshTokenOrThrow(token, REFRESH_LEN); // throws BadRequest REFRESH_TOKEN__INVALID_OR_EXPIRED
|
|
145
|
+
```
|
|
146
|
+
|
|
100
147
|
### `PasswordUtils`
|
|
101
148
|
|
|
102
149
|
Bcrypt hash/compare and simple length checks.
|
|
@@ -264,7 +311,7 @@ const x = toNumber(unknownValue, 0);
|
|
|
264
311
|
|
|
265
312
|
### `HttpCodes` / `ErrorClasses` / `ECApiCodes`
|
|
266
313
|
|
|
267
|
-
HTTP status constants and errors for APIs: `ApiError` plus `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `TooManyRequests`, `InternalServerError`.
|
|
314
|
+
HTTP status constants and errors for APIs: `ApiError` plus `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `TooManyRequests`, `InternalServerError`. Error `message` values use the `ECApiCodes` string union—machine-readable codes for clients (including auth/JWT/refresh, captcha, password and field validation, slug/username/profile fields, and simple success-style API codes such as `FETCHED` or `CREATED`).
|
|
268
315
|
|
|
269
316
|
```ts
|
|
270
317
|
import { HttpCodes } from "@knsdev/node-utils/HttpCodes.js";
|
|
@@ -362,7 +409,7 @@ Bengali digit substitution and English month → Bengali month names.
|
|
|
362
409
|
```ts
|
|
363
410
|
import { bnNum, monthToBn } from "@knsdev/node-utils/bnUtils.js";
|
|
364
411
|
|
|
365
|
-
bnNum(
|
|
412
|
+
bnNum(2026); // Bengali digits for "2026"
|
|
366
413
|
monthToBn("january");
|
|
367
414
|
monthToBn(3);
|
|
368
415
|
```
|
|
@@ -397,12 +444,12 @@ const LoginBody = z.object({
|
|
|
397
444
|
|
|
398
445
|
## Dependencies (summary)
|
|
399
446
|
|
|
400
|
-
| Area
|
|
401
|
-
|
|
|
402
|
-
| JWT
|
|
403
|
-
| Google ID token
|
|
404
|
-
| Passwords
|
|
405
|
-
| Schemas
|
|
447
|
+
| Area | Packages |
|
|
448
|
+
| ------------------------- | ------------------------------ |
|
|
449
|
+
| JWT | `jsonwebtoken` |
|
|
450
|
+
| Google ID token | `jose` |
|
|
451
|
+
| Passwords | `bcrypt` |
|
|
452
|
+
| Schemas | `zod` |
|
|
406
453
|
| Cookies + Express helpers | `express` (types / `Response`) |
|
|
407
454
|
|
|
408
455
|
---
|
package/dist/ECApiCodes.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type ECApiCodes = `API_ERROR` | `CREATED` | `UPDATED` | `DELETED` | `FETCHED` | `VALIDATED` | `AUTHENTICATED` | `SUCCESS` | `BAD_REQUEST` | `AUTHORIZED` | `UNAUTHORIZED` | `FORBIDDEN` | `NOT_FOUND` | `INVALID_PASSWORD_OR_USER_NOT_FOUND` | `INTERNAL_SERVER_ERROR` | `TOO_MANY_REQUESTS` | `JWT__ENCRYPTION_FAILED` | `JWT__INVALID_OR_EXPIRED` | `CAPTCHA__VALIDATION_FAILED` | `PASSWORD__INVALID` | `PASSWORD__MIN_LENGTH` | `PASSWORD__MAX_LENGTH` | `GENERAL_TOKEN__INVALID` | `GENERAL_TOKEN__LENGTH` | `CODE_6_DIGITS__INVALID` | `CODE_6_DIGITS__LENGTH` | `CODE_6_DIGITS__INVALID_FORMAT` | `EMAIL__INVALID` | `EMAIL__MIN_LENGTH` | `EMAIL__MAX_LENGTH` | `FIRST_NAME__INVALID` | `FIRST_NAME__MIN_LENGTH` | `FIRST_NAME__MAX_LENGTH` | `LAST_NAME__INVALID` | `LAST_NAME__MAX_LENGTH` | `TURNSTILE_TOKEN__INVALID` | `TURNSTILE_TOKEN__MIN_LENGTH` | `TURNSTILE_TOKEN__MAX_LENGTH` | `CONFIRM_PASSWORD__DONT_MATCH` | `LOGOUT_OTHER_SESSIONS__INVALID` | `REMEMBER_ME__INVALID` | `SLUG__MIN_LENGTH` | `SLUG__MAX_LENGTH` | `SLUG__NAME__MIN_LENGTH` | `SLUG__NAME__MAX_LENGTH` | `SLUG__DESCRIPTION__MIN_LENGTH` | `SLUG__DESCRIPTION__MAX_LENGTH` | `SLUG__INVALID` | `USERNAME__INVALID` | `USERNAME__MIN_LENGTH` | `USERNAME__MAX_LENGTH` | `DESIGNATION__INVALID` | `DESIGNATION_MAX_LENGTH` | `BIO__INVALID` | `BIO_MAX_LENGTH` | `PROFILE_VISIBILITY__INVALID` | `ID__INVALID`;
|
|
1
|
+
export type ECApiCodes = `API_ERROR` | `CREATED` | `UPDATED` | `DELETED` | `FETCHED` | `VALIDATED` | `AUTHENTICATED` | `SUCCESS` | `BAD_REQUEST` | `AUTHORIZED` | `UNAUTHORIZED` | `FORBIDDEN` | `NOT_FOUND` | `INVALID_PASSWORD_OR_USER_NOT_FOUND` | `REFRESH_TOKEN__INVALID` | `REFRESH_TOKEN__EXPIRED` | `REFRESH_TOKEN__INVALID_OR_EXPIRED` | `INTERNAL_SERVER_ERROR` | `TOO_MANY_REQUESTS` | `JWT__ENCRYPTION_FAILED` | `JWT__INVALID_OR_EXPIRED` | `CAPTCHA__VALIDATION_FAILED` | `PASSWORD__INVALID` | `PASSWORD__MIN_LENGTH` | `PASSWORD__MAX_LENGTH` | `GENERAL_TOKEN__INVALID` | `GENERAL_TOKEN__LENGTH` | `CODE_6_DIGITS__INVALID` | `CODE_6_DIGITS__LENGTH` | `CODE_6_DIGITS__INVALID_FORMAT` | `EMAIL__INVALID` | `EMAIL__MIN_LENGTH` | `EMAIL__MAX_LENGTH` | `FIRST_NAME__INVALID` | `FIRST_NAME__MIN_LENGTH` | `FIRST_NAME__MAX_LENGTH` | `LAST_NAME__INVALID` | `LAST_NAME__MAX_LENGTH` | `TURNSTILE_TOKEN__INVALID` | `TURNSTILE_TOKEN__MIN_LENGTH` | `TURNSTILE_TOKEN__MAX_LENGTH` | `CONFIRM_PASSWORD__DONT_MATCH` | `LOGOUT_OTHER_SESSIONS__INVALID` | `REMEMBER_ME__INVALID` | `SLUG__MIN_LENGTH` | `SLUG__MAX_LENGTH` | `SLUG__NAME__MIN_LENGTH` | `SLUG__NAME__MAX_LENGTH` | `SLUG__DESCRIPTION__MIN_LENGTH` | `SLUG__DESCRIPTION__MAX_LENGTH` | `SLUG__INVALID` | `USERNAME__INVALID` | `USERNAME__MIN_LENGTH` | `USERNAME__MAX_LENGTH` | `DESIGNATION__INVALID` | `DESIGNATION_MAX_LENGTH` | `BIO__INVALID` | `BIO_MAX_LENGTH` | `PROFILE_VISIBILITY__INVALID` | `ID__INVALID`;
|
|
2
2
|
//# sourceMappingURL=ECApiCodes.d.ts.map
|
package/dist/ECApiCodes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ECApiCodes.d.ts","sourceRoot":"","sources":["../src/ECApiCodes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,WAAW,GACnC,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,WAAW,GACX,eAAe,GACf,SAAS,GACT,aAAa,GACb,YAAY,GACZ,cAAc,GACd,WAAW,GACX,WAAW,GACX,oCAAoC,GACpC,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,GACxB,yBAAyB,GACzB,4BAA4B,GAC5B,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,wBAAwB,GACxB,uBAAuB,GACvB,wBAAwB,GACxB,uBAAuB,GACvB,+BAA+B,GAC/B,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,qBAAqB,GACrB,wBAAwB,GACxB,wBAAwB,GACxB,oBAAoB,GACpB,uBAAuB,GACvB,0BAA0B,GAC1B,6BAA6B,GAC7B,6BAA6B,GAC7B,8BAA8B,GAC9B,gCAAgC,GAChC,sBAAsB,GACtB,kBAAkB,GAClB,kBAAkB,GAClB,wBAAwB,GACxB,wBAAwB,GACxB,+BAA+B,GAC/B,+BAA+B,GAC/B,eAAe,GACf,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,wBAAwB,GACxB,cAAc,GACd,gBAAgB,GAChB,6BAA6B,GAC7B,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"ECApiCodes.d.ts","sourceRoot":"","sources":["../src/ECApiCodes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,WAAW,GACnC,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,WAAW,GACX,eAAe,GACf,SAAS,GACT,aAAa,GACb,YAAY,GACZ,cAAc,GACd,WAAW,GACX,WAAW,GACX,oCAAoC,GACpC,wBAAwB,GACxB,wBAAwB,GACxB,mCAAmC,GACnC,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,GACxB,yBAAyB,GACzB,4BAA4B,GAC5B,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,wBAAwB,GACxB,uBAAuB,GACvB,wBAAwB,GACxB,uBAAuB,GACvB,+BAA+B,GAC/B,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,qBAAqB,GACrB,wBAAwB,GACxB,wBAAwB,GACxB,oBAAoB,GACpB,uBAAuB,GACvB,0BAA0B,GAC1B,6BAA6B,GAC7B,6BAA6B,GAC7B,8BAA8B,GAC9B,gCAAgC,GAChC,sBAAsB,GACtB,kBAAkB,GAClB,kBAAkB,GAClB,wBAAwB,GACxB,wBAAwB,GACxB,+BAA+B,GAC/B,+BAA+B,GAC/B,eAAe,GACf,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,wBAAwB,GACxB,cAAc,GACd,gBAAgB,GAChB,6BAA6B,GAC7B,aAAa,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token utilities for use across projects.
|
|
3
|
+
* Import via: `@knsdev/node-utils/TokenUtils`
|
|
4
|
+
*/
|
|
5
|
+
declare class tokenUtils {
|
|
6
|
+
private readonly BASE58_CHARS;
|
|
7
|
+
private readonly base;
|
|
8
|
+
readonly genAccessToken: <T extends string | object | Buffer<ArrayBufferLike> = "Error: a type template is required">(jwtSecret: string, data: T, expiresMs: number) => string;
|
|
9
|
+
readonly genTokens: <T extends string | object | Buffer<ArrayBufferLike> = "Error: a type template is required">(jwtSecret: string, data: T, expiresMs: number) => {
|
|
10
|
+
data: {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
refreshToken: string;
|
|
13
|
+
accessTokenHash: string;
|
|
14
|
+
refreshTokenHash: string;
|
|
15
|
+
};
|
|
16
|
+
error: null;
|
|
17
|
+
} | {
|
|
18
|
+
data: null;
|
|
19
|
+
error: Error;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Decode an access token and return the user data.
|
|
23
|
+
* @param jwtSecret - The JWT secret.
|
|
24
|
+
* @param accessToken - The access token to decode.
|
|
25
|
+
* @returns The user data.
|
|
26
|
+
* @throws {InternalServerError} If the access token is invalid or expired.
|
|
27
|
+
*/
|
|
28
|
+
readonly decodeAccessToken: <T extends string | object | Buffer<ArrayBufferLike> = "Error: a type template is required">(jwtSecret: string, accessToken: unknown) => {
|
|
29
|
+
data: T;
|
|
30
|
+
error: null;
|
|
31
|
+
} | {
|
|
32
|
+
data: null;
|
|
33
|
+
error: Error;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Decode an access token and return the user data.
|
|
37
|
+
* @param jwtSecret - The JWT secret.
|
|
38
|
+
* @param accessToken - The access token to decode.
|
|
39
|
+
* @returns The user data.
|
|
40
|
+
* @throws {InternalServerError} If the access token is invalid or expired.
|
|
41
|
+
*/
|
|
42
|
+
readonly decodeAccessTokenOrThrow: <T extends string | object | Buffer<ArrayBufferLike> = "Error: a type template is required">(jwtSecret: string, accessToken: unknown) => T;
|
|
43
|
+
/**
|
|
44
|
+
* Generate a refresh token.
|
|
45
|
+
* @returns The refresh token.
|
|
46
|
+
*/
|
|
47
|
+
private readonly genRefreshToken;
|
|
48
|
+
/**
|
|
49
|
+
* Validate a refresh token
|
|
50
|
+
* @param refreshToken - The refresh token to validate.
|
|
51
|
+
* @param length - The length of the refresh token.
|
|
52
|
+
* @returns True if the refresh token is valid, false otherwise.
|
|
53
|
+
*/
|
|
54
|
+
readonly validateRefreshToken: (refreshToken: unknown, length: number) => refreshToken is string;
|
|
55
|
+
/**
|
|
56
|
+
* Validate a refresh token and throw an error if it is invalid or expired.
|
|
57
|
+
* @param refreshToken - The refresh token to validate.
|
|
58
|
+
* @param length - The length of the refresh token.
|
|
59
|
+
* @returns True if the refresh token is valid, false otherwise.
|
|
60
|
+
* @throws {BadRequest} If the refresh token is invalid or expired.
|
|
61
|
+
*/
|
|
62
|
+
readonly validateRefreshTokenOrThrow: (refreshToken: unknown, length: number) => string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Token utilities for use across projects.
|
|
66
|
+
* Import via: `@knsdev/node-utils/TokenUtils`
|
|
67
|
+
*/
|
|
68
|
+
export declare const TokenUtils: tokenUtils;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=TokenUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenUtils.d.ts","sourceRoot":"","sources":["../src/TokenUtils.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,cAAM,UAAU;IACf,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgE;IAC7F,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IAGjD,QAAQ,CAAC,cAAc,GAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,GAAG,oCAAoC,EACpH,WAAW,MAAM,EACjB,MAAM,CAAC,EACP,WAAW,MAAM,KACf,MAAM,CAGR;IAED,QAAQ,CAAC,SAAS,GAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,GAAG,oCAAoC,EAC/G,WAAW,MAAM,EACjB,MAAM,CAAC,EACP,WAAW,MAAM,KACf;QACF,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,eAAe,EAAE,MAAM,CAAC;YAAC,gBAAgB,EAAE,MAAM,CAAA;SAAE,CAAC;QACvG,KAAK,EAAE,IAAI,CAAA;KACX,GAAG;QACH,IAAI,EAAE,IAAI,CAAC;QACX,KAAK,EAAE,KAAK,CAAA;KACZ,CAWA;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,GAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,GAAG,oCAAoC,EAAE,WAAW,MAAM,EAAE,aAAa,OAAO,KAAG;QACnK,IAAI,EAAE,CAAC,CAAC;QACR,KAAK,EAAE,IAAI,CAAA;KACX,GAAG;QACH,IAAI,EAAE,IAAI,CAAC;QACX,KAAK,EAAE,KAAK,CAAA;KACZ,CAOA;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,GAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,GAAG,oCAAoC,EAAE,WAAW,MAAM,EAAE,aAAa,OAAO,KAAG,CAAC,CAE3K;IAED;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAc/B;IAED;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,GAAI,cAAc,OAAO,EAAE,QAAQ,MAAM,KAAG,YAAY,IAAI,MAAM,CAU9F;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,2BAA2B,GAAI,cAAc,OAAO,EAAE,QAAQ,MAAM,KAAG,MAAM,CAKrF;CAED;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,YAAmB,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import { HMACUtils } from "./HMACUtils.js";
|
|
3
|
+
import { BadRequest, InternalServerError } from "./ErrorClasses.js";
|
|
4
|
+
import { JwtUtils } from "./JwtUtils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Token utilities for use across projects.
|
|
7
|
+
* Import via: `@knsdev/node-utils/TokenUtils`
|
|
8
|
+
*/
|
|
9
|
+
class tokenUtils {
|
|
10
|
+
BASE58_CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
11
|
+
base = this.BASE58_CHARS.length;
|
|
12
|
+
genAccessToken = (jwtSecret, data, expiresMs) => {
|
|
13
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
14
|
+
return JwtUtils.signOrThrow(jwtSecret, data, expiresMs);
|
|
15
|
+
};
|
|
16
|
+
genTokens = (jwtSecret, data, expiresMs) => {
|
|
17
|
+
try {
|
|
18
|
+
const accessToken = this.genAccessToken(jwtSecret, data, expiresMs);
|
|
19
|
+
const refreshToken = this.genRefreshToken();
|
|
20
|
+
const accessTokenHash = HMACUtils.hashOrThrow(jwtSecret, accessToken);
|
|
21
|
+
const refreshTokenHash = HMACUtils.hashOrThrow(jwtSecret, refreshToken);
|
|
22
|
+
return { data: { accessToken, refreshToken, accessTokenHash, refreshTokenHash }, error: null };
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { data: null, error: new InternalServerError('JWT__ENCRYPTION_FAILED') };
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Decode an access token and return the user data.
|
|
30
|
+
* @param jwtSecret - The JWT secret.
|
|
31
|
+
* @param accessToken - The access token to decode.
|
|
32
|
+
* @returns The user data.
|
|
33
|
+
* @throws {InternalServerError} If the access token is invalid or expired.
|
|
34
|
+
*/
|
|
35
|
+
decodeAccessToken = (jwtSecret, accessToken) => {
|
|
36
|
+
try {
|
|
37
|
+
return { data: this.decodeAccessTokenOrThrow(jwtSecret, accessToken), error: null };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { data: null, error: new InternalServerError('JWT__INVALID_OR_EXPIRED') };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Decode an access token and return the user data.
|
|
45
|
+
* @param jwtSecret - The JWT secret.
|
|
46
|
+
* @param accessToken - The access token to decode.
|
|
47
|
+
* @returns The user data.
|
|
48
|
+
* @throws {InternalServerError} If the access token is invalid or expired.
|
|
49
|
+
*/
|
|
50
|
+
decodeAccessTokenOrThrow = (jwtSecret, accessToken) => {
|
|
51
|
+
return JwtUtils.verifyOrThrow(jwtSecret, accessToken);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Generate a refresh token.
|
|
55
|
+
* @returns The refresh token.
|
|
56
|
+
*/
|
|
57
|
+
genRefreshToken = () => {
|
|
58
|
+
const REFRESH_TOKEN_LENGTH = 64;
|
|
59
|
+
const max = 256 - (256 % this.base); // avoid modulo bias (252)
|
|
60
|
+
let out = '';
|
|
61
|
+
while (out.length < REFRESH_TOKEN_LENGTH) {
|
|
62
|
+
const buf = crypto.randomBytes(32);
|
|
63
|
+
for (let i = 0; i < buf.length && out.length < REFRESH_TOKEN_LENGTH; i++) {
|
|
64
|
+
const v = buf[i];
|
|
65
|
+
if (v >= max)
|
|
66
|
+
continue;
|
|
67
|
+
out += this.BASE58_CHARS[v % this.base];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Validate a refresh token
|
|
74
|
+
* @param refreshToken - The refresh token to validate.
|
|
75
|
+
* @param length - The length of the refresh token.
|
|
76
|
+
* @returns True if the refresh token is valid, false otherwise.
|
|
77
|
+
*/
|
|
78
|
+
validateRefreshToken = (refreshToken, length) => {
|
|
79
|
+
if (typeof refreshToken !== 'string' || refreshToken.length !== length) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
for (let i = 0; i < refreshToken.length; i++) {
|
|
83
|
+
if (!this.BASE58_CHARS.includes(refreshToken[i])) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Validate a refresh token and throw an error if it is invalid or expired.
|
|
91
|
+
* @param refreshToken - The refresh token to validate.
|
|
92
|
+
* @param length - The length of the refresh token.
|
|
93
|
+
* @returns True if the refresh token is valid, false otherwise.
|
|
94
|
+
* @throws {BadRequest} If the refresh token is invalid or expired.
|
|
95
|
+
*/
|
|
96
|
+
validateRefreshTokenOrThrow = (refreshToken, length) => {
|
|
97
|
+
if (!this.validateRefreshToken(refreshToken, length)) {
|
|
98
|
+
throw new BadRequest('REFRESH_TOKEN__INVALID_OR_EXPIRED');
|
|
99
|
+
}
|
|
100
|
+
return refreshToken;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Token utilities for use across projects.
|
|
105
|
+
* Import via: `@knsdev/node-utils/TokenUtils`
|
|
106
|
+
*/
|
|
107
|
+
export const TokenUtils = new tokenUtils();
|
|
108
|
+
//# sourceMappingURL=TokenUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenUtils.js","sourceRoot":"","sources":["../src/TokenUtils.ts"],"names":[],"mappings":"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"}
|