@naman_deep_singh/security 1.3.0 → 1.3.1
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 +1 -1
- package/dist/cjs/core/crypto/cryptoManager.js +9 -3
- package/dist/cjs/core/crypto/decrypt.js +6 -6
- package/dist/cjs/core/crypto/encrypt.js +4 -4
- package/dist/cjs/core/crypto/hmac.js +1 -1
- package/dist/cjs/core/crypto/index.d.ts +5 -5
- package/dist/cjs/core/crypto/random.js +2 -2
- package/dist/cjs/core/jwt/decode.d.ts +1 -1
- package/dist/cjs/core/jwt/decode.js +2 -2
- package/dist/cjs/core/jwt/extractToken.js +7 -7
- package/dist/cjs/core/jwt/generateTokens.d.ts +2 -2
- package/dist/cjs/core/jwt/generateTokens.js +10 -6
- package/dist/cjs/core/jwt/index.d.ts +8 -8
- package/dist/cjs/core/jwt/jwtManager.d.ts +3 -2
- package/dist/cjs/core/jwt/jwtManager.js +66 -86
- package/dist/cjs/core/jwt/parseDuration.js +3 -3
- package/dist/cjs/core/jwt/signToken.d.ts +1 -1
- package/dist/cjs/core/jwt/signToken.js +7 -7
- package/dist/cjs/core/jwt/types.d.ts +1 -1
- package/dist/cjs/core/jwt/validateToken.d.ts +2 -2
- package/dist/cjs/core/jwt/validateToken.js +3 -3
- package/dist/cjs/core/jwt/verify.d.ts +3 -2
- package/dist/cjs/core/password/hash.js +1 -1
- package/dist/cjs/core/password/index.d.ts +3 -3
- package/dist/cjs/core/password/passwordManager.d.ts +1 -1
- package/dist/cjs/core/password/passwordManager.js +32 -31
- package/dist/cjs/core/password/strength.d.ts +1 -1
- package/dist/cjs/core/password/strength.js +4 -4
- package/dist/cjs/core/password/utils.js +2 -2
- package/dist/cjs/core/password/verify.js +1 -1
- package/dist/cjs/index.d.ts +6 -6
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/interfaces/jwt.interface.d.ts +1 -1
- package/dist/esm/core/crypto/cryptoManager.js +10 -4
- package/dist/esm/core/crypto/decrypt.js +7 -7
- package/dist/esm/core/crypto/encrypt.js +5 -5
- package/dist/esm/core/crypto/hmac.js +2 -2
- package/dist/esm/core/crypto/index.d.ts +5 -5
- package/dist/esm/core/crypto/index.js +5 -5
- package/dist/esm/core/crypto/random.js +3 -3
- package/dist/esm/core/jwt/decode.d.ts +1 -1
- package/dist/esm/core/jwt/decode.js +3 -3
- package/dist/esm/core/jwt/extractToken.js +7 -7
- package/dist/esm/core/jwt/generateTokens.d.ts +2 -2
- package/dist/esm/core/jwt/generateTokens.js +12 -8
- package/dist/esm/core/jwt/index.d.ts +8 -8
- package/dist/esm/core/jwt/index.js +8 -8
- package/dist/esm/core/jwt/jwtManager.d.ts +3 -2
- package/dist/esm/core/jwt/jwtManager.js +70 -90
- package/dist/esm/core/jwt/parseDuration.js +3 -3
- package/dist/esm/core/jwt/signToken.d.ts +1 -1
- package/dist/esm/core/jwt/signToken.js +9 -9
- package/dist/esm/core/jwt/types.d.ts +1 -1
- package/dist/esm/core/jwt/validateToken.d.ts +2 -2
- package/dist/esm/core/jwt/validateToken.js +3 -3
- package/dist/esm/core/jwt/verify.d.ts +3 -2
- package/dist/esm/core/jwt/verify.js +1 -1
- package/dist/esm/core/password/hash.js +3 -3
- package/dist/esm/core/password/index.d.ts +3 -3
- package/dist/esm/core/password/index.js +3 -3
- package/dist/esm/core/password/passwordManager.d.ts +1 -1
- package/dist/esm/core/password/passwordManager.js +34 -33
- package/dist/esm/core/password/strength.d.ts +1 -1
- package/dist/esm/core/password/strength.js +5 -5
- package/dist/esm/core/password/utils.js +4 -4
- package/dist/esm/core/password/verify.js +2 -2
- package/dist/esm/index.d.ts +6 -6
- package/dist/esm/index.js +7 -7
- package/dist/esm/interfaces/jwt.interface.d.ts +1 -1
- package/dist/types/core/crypto/index.d.ts +5 -5
- package/dist/types/core/jwt/decode.d.ts +1 -1
- package/dist/types/core/jwt/generateTokens.d.ts +2 -2
- package/dist/types/core/jwt/index.d.ts +8 -8
- package/dist/types/core/jwt/jwtManager.d.ts +3 -2
- package/dist/types/core/jwt/signToken.d.ts +1 -1
- package/dist/types/core/jwt/types.d.ts +1 -1
- package/dist/types/core/jwt/validateToken.d.ts +2 -2
- package/dist/types/core/jwt/verify.d.ts +3 -2
- package/dist/types/core/password/index.d.ts +3 -3
- package/dist/types/core/password/passwordManager.d.ts +1 -1
- package/dist/types/core/password/strength.d.ts +1 -1
- package/dist/types/index.d.ts +6 -6
- package/dist/types/interfaces/jwt.interface.d.ts +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ const index_1 = require("./index");
|
|
|
8
8
|
const DEFAULT_CONFIG = {
|
|
9
9
|
defaultAlgorithm: 'aes-256-gcm',
|
|
10
10
|
defaultEncoding: 'utf8',
|
|
11
|
-
hmacAlgorithm: 'sha256'
|
|
11
|
+
hmacAlgorithm: 'sha256',
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* CryptoManager - Class-based wrapper for all cryptographic operations
|
|
@@ -106,8 +106,14 @@ class CryptoManager {
|
|
|
106
106
|
const crypto = require('crypto');
|
|
107
107
|
const keyPair = crypto.generateKeyPairSync('rsa', {
|
|
108
108
|
modulusLength: options?.modulusLength || 2048,
|
|
109
|
-
publicKeyEncoding: options?.publicKeyEncoding || {
|
|
110
|
-
|
|
109
|
+
publicKeyEncoding: options?.publicKeyEncoding || {
|
|
110
|
+
type: 'spki',
|
|
111
|
+
format: 'pem',
|
|
112
|
+
},
|
|
113
|
+
privateKeyEncoding: options?.privateKeyEncoding || {
|
|
114
|
+
type: 'pkcs8',
|
|
115
|
+
format: 'pem',
|
|
116
|
+
},
|
|
111
117
|
});
|
|
112
118
|
resolve(keyPair);
|
|
113
119
|
});
|
|
@@ -5,17 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.decrypt = void 0;
|
|
7
7
|
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
-
const ALGO =
|
|
8
|
+
const ALGO = 'AES-256-GCM';
|
|
9
9
|
const decrypt = (data, secret) => {
|
|
10
|
-
const [ivHex, encryptedHex] = data.split(
|
|
11
|
-
const iv = Buffer.from(ivHex,
|
|
12
|
-
const encrypted = Buffer.from(encryptedHex,
|
|
13
|
-
const key = crypto_1.default.createHash(
|
|
10
|
+
const [ivHex, encryptedHex] = data.split(':');
|
|
11
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
12
|
+
const encrypted = Buffer.from(encryptedHex, 'hex');
|
|
13
|
+
const key = crypto_1.default.createHash('sha256').update(secret).digest();
|
|
14
14
|
const decipher = crypto_1.default.createDecipheriv(ALGO, key, iv);
|
|
15
15
|
const decrypted = Buffer.concat([
|
|
16
16
|
decipher.update(encrypted),
|
|
17
17
|
decipher.final(),
|
|
18
18
|
]);
|
|
19
|
-
return decrypted.toString(
|
|
19
|
+
return decrypted.toString('utf8');
|
|
20
20
|
};
|
|
21
21
|
exports.decrypt = decrypt;
|
|
@@ -5,12 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.encrypt = void 0;
|
|
7
7
|
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
-
const ALGO =
|
|
8
|
+
const ALGO = 'AES-256-GCM';
|
|
9
9
|
const encrypt = (text, secret) => {
|
|
10
|
-
const key = crypto_1.default.createHash(
|
|
10
|
+
const key = crypto_1.default.createHash('sha256').update(secret).digest();
|
|
11
11
|
const iv = crypto_1.default.randomBytes(16);
|
|
12
12
|
const cipher = crypto_1.default.createCipheriv(ALGO, key, iv);
|
|
13
|
-
const encrypted = Buffer.concat([cipher.update(text,
|
|
14
|
-
return `${iv.toString(
|
|
13
|
+
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
14
|
+
return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
|
|
15
15
|
};
|
|
16
16
|
exports.encrypt = encrypt;
|
|
@@ -9,7 +9,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
9
9
|
* Sign message using HMAC SHA-256
|
|
10
10
|
*/
|
|
11
11
|
const hmacSign = (message, secret) => {
|
|
12
|
-
return crypto_1.default.createHmac(
|
|
12
|
+
return crypto_1.default.createHmac('sha256', secret).update(message).digest('hex');
|
|
13
13
|
};
|
|
14
14
|
exports.hmacSign = hmacSign;
|
|
15
15
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { decrypt } from
|
|
2
|
-
export { encrypt } from
|
|
3
|
-
export { hmacSign, hmacVerify } from
|
|
4
|
-
export { randomToken, generateStrongPassword } from
|
|
5
|
-
export * from
|
|
1
|
+
export { decrypt } from './decrypt';
|
|
2
|
+
export { encrypt } from './encrypt';
|
|
3
|
+
export { hmacSign, hmacVerify } from './hmac';
|
|
4
|
+
export { randomToken, generateStrongPassword } from './random';
|
|
5
|
+
export * from './cryptoManager';
|
|
@@ -9,13 +9,13 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
9
9
|
* Generate cryptographically secure random string
|
|
10
10
|
*/
|
|
11
11
|
const randomToken = (length = 32) => {
|
|
12
|
-
return crypto_1.default.randomBytes(length).toString(
|
|
12
|
+
return crypto_1.default.randomBytes(length).toString('hex');
|
|
13
13
|
};
|
|
14
14
|
exports.randomToken = randomToken;
|
|
15
15
|
/**
|
|
16
16
|
* Generate a strong random password
|
|
17
17
|
*/
|
|
18
18
|
const generateStrongPassword = (length = 16) => {
|
|
19
|
-
return crypto_1.default.randomBytes(length).toString(
|
|
19
|
+
return crypto_1.default.randomBytes(length).toString('hex').slice(0, length);
|
|
20
20
|
};
|
|
21
21
|
exports.generateStrongPassword = generateStrongPassword;
|
|
@@ -18,8 +18,8 @@ function decodeToken(token) {
|
|
|
18
18
|
*/
|
|
19
19
|
function decodeTokenStrict(token) {
|
|
20
20
|
const decoded = (0, jsonwebtoken_1.decode)(token);
|
|
21
|
-
if (!decoded || typeof decoded ===
|
|
22
|
-
throw new Error(
|
|
21
|
+
if (!decoded || typeof decoded === 'string') {
|
|
22
|
+
throw new Error('Invalid JWT payload structure');
|
|
23
23
|
}
|
|
24
24
|
return decoded;
|
|
25
25
|
}
|
|
@@ -8,16 +8,16 @@ function extractToken(sources) {
|
|
|
8
8
|
const { header, cookies, query, body, wsMessage } = sources;
|
|
9
9
|
// 1. Authorization: Bearer <token>
|
|
10
10
|
if (header) {
|
|
11
|
-
const parts = header.split(
|
|
12
|
-
if (parts.length === 2 && parts[0] ===
|
|
11
|
+
const parts = header.split(' ');
|
|
12
|
+
if (parts.length === 2 && parts[0] === 'Bearer')
|
|
13
13
|
return parts[1];
|
|
14
14
|
}
|
|
15
15
|
// 2. Cookies: token / accessToken
|
|
16
16
|
if (cookies) {
|
|
17
|
-
if (cookies[
|
|
18
|
-
return cookies[
|
|
19
|
-
if (cookies[
|
|
20
|
-
return cookies[
|
|
17
|
+
if (cookies['token'])
|
|
18
|
+
return cookies['token'];
|
|
19
|
+
if (cookies['accessToken'])
|
|
20
|
+
return cookies['accessToken'];
|
|
21
21
|
}
|
|
22
22
|
// 3. Query params: ?token=xxx
|
|
23
23
|
if (query?.token)
|
|
@@ -30,7 +30,7 @@ function extractToken(sources) {
|
|
|
30
30
|
try {
|
|
31
31
|
let msg = wsMessage;
|
|
32
32
|
// If it's a JSON string → parse safely
|
|
33
|
-
if (typeof wsMessage ===
|
|
33
|
+
if (typeof wsMessage === 'string') {
|
|
34
34
|
msg = JSON.parse(wsMessage);
|
|
35
35
|
}
|
|
36
36
|
// Ensure msg is an object before property access
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Secret } from
|
|
2
|
-
import { RefreshToken, TokenPair } from
|
|
1
|
+
import { type Secret } from 'jsonwebtoken';
|
|
2
|
+
import type { RefreshToken, TokenPair } from './types';
|
|
3
3
|
export declare const generateTokens: (payload: Record<string, unknown>, accessSecret: Secret, refreshSecret: Secret, accessExpiry?: string | number, refreshExpiry?: string | number) => TokenPair;
|
|
4
4
|
export declare function rotateRefreshToken(oldToken: string, secret: Secret): RefreshToken;
|
|
@@ -8,9 +8,13 @@ const verify_1 = require("./verify");
|
|
|
8
8
|
const createBrandedToken = (token, _brand) => {
|
|
9
9
|
return token;
|
|
10
10
|
};
|
|
11
|
-
const generateTokens = (payload, accessSecret, refreshSecret, accessExpiry =
|
|
12
|
-
const accessToken = (0, signToken_1.signToken)(payload, accessSecret, accessExpiry, {
|
|
13
|
-
|
|
11
|
+
const generateTokens = (payload, accessSecret, refreshSecret, accessExpiry = '15m', refreshExpiry = '7d') => {
|
|
12
|
+
const accessToken = (0, signToken_1.signToken)(payload, accessSecret, accessExpiry, {
|
|
13
|
+
algorithm: 'HS256',
|
|
14
|
+
});
|
|
15
|
+
const refreshToken = (0, signToken_1.signToken)(payload, refreshSecret, refreshExpiry, {
|
|
16
|
+
algorithm: 'HS256',
|
|
17
|
+
});
|
|
14
18
|
return {
|
|
15
19
|
accessToken: accessToken,
|
|
16
20
|
refreshToken: refreshToken,
|
|
@@ -19,12 +23,12 @@ const generateTokens = (payload, accessSecret, refreshSecret, accessExpiry = "15
|
|
|
19
23
|
exports.generateTokens = generateTokens;
|
|
20
24
|
function rotateRefreshToken(oldToken, secret) {
|
|
21
25
|
const decoded = (0, verify_1.verifyToken)(oldToken, secret);
|
|
22
|
-
if (typeof decoded ===
|
|
23
|
-
throw new Error(
|
|
26
|
+
if (typeof decoded === 'string') {
|
|
27
|
+
throw new Error('Invalid token payload — expected JWT payload object');
|
|
24
28
|
}
|
|
25
29
|
const payload = { ...decoded };
|
|
26
30
|
delete payload.iat;
|
|
27
31
|
delete payload.exp;
|
|
28
|
-
const newToken = (0, signToken_1.signToken)(payload, secret,
|
|
32
|
+
const newToken = (0, signToken_1.signToken)(payload, secret, '7d');
|
|
29
33
|
return newToken;
|
|
30
34
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
1
|
+
export * from './decode';
|
|
2
|
+
export * from './extractToken';
|
|
3
|
+
export * from './generateTokens';
|
|
4
|
+
export * from './parseDuration';
|
|
5
|
+
export * from './signToken';
|
|
6
|
+
export * from './types';
|
|
7
|
+
export * from './validateToken';
|
|
8
|
+
export * from './verify';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { JwtPayload, Secret } from
|
|
2
|
-
import {
|
|
1
|
+
import { type JwtPayload, type Secret } from 'jsonwebtoken';
|
|
2
|
+
import type { AccessToken, ITokenManager, JWTConfig, RefreshToken, TokenPair, TokenValidationOptions } from '../../interfaces/jwt.interface';
|
|
3
3
|
export declare class JWTManager implements ITokenManager {
|
|
4
4
|
private accessSecret;
|
|
5
5
|
private refreshSecret;
|
|
6
6
|
private accessExpiry;
|
|
7
7
|
private refreshExpiry;
|
|
8
8
|
private cache?;
|
|
9
|
+
private cacheTTL;
|
|
9
10
|
constructor(config: JWTConfig);
|
|
10
11
|
/**
|
|
11
12
|
* Generate both access and refresh tokens
|
|
@@ -8,51 +8,16 @@ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
|
8
8
|
const signToken_1 = require("./signToken");
|
|
9
9
|
const verify_1 = require("./verify");
|
|
10
10
|
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
11
|
-
|
|
12
|
-
class TokenCache {
|
|
13
|
-
constructor(maxSize = 100, ttl = 5 * 60 * 1000) {
|
|
14
|
-
this.cache = new Map();
|
|
15
|
-
this.maxSize = maxSize;
|
|
16
|
-
this.ttl = ttl;
|
|
17
|
-
}
|
|
18
|
-
get(key) {
|
|
19
|
-
const entry = this.cache.get(key);
|
|
20
|
-
if (!entry)
|
|
21
|
-
return null;
|
|
22
|
-
if (Date.now() - entry.timestamp > this.ttl) {
|
|
23
|
-
this.cache.delete(key);
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
// Move to end (most recently used)
|
|
27
|
-
this.cache.delete(key);
|
|
28
|
-
this.cache.set(key, entry);
|
|
29
|
-
return { valid: entry.valid, payload: entry.payload };
|
|
30
|
-
}
|
|
31
|
-
set(key, value) {
|
|
32
|
-
if (this.cache.has(key)) {
|
|
33
|
-
this.cache.delete(key);
|
|
34
|
-
}
|
|
35
|
-
else if (this.cache.size >= this.maxSize) {
|
|
36
|
-
// Remove least recently used (first item)
|
|
37
|
-
const firstKey = this.cache.keys().next().value;
|
|
38
|
-
if (firstKey !== undefined) {
|
|
39
|
-
this.cache.delete(firstKey);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
this.cache.set(key, { ...value, timestamp: Date.now() });
|
|
43
|
-
}
|
|
44
|
-
clear() {
|
|
45
|
-
this.cache.clear();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
11
|
+
const js_extensions_1 = require("@naman_deep_singh/js-extensions");
|
|
48
12
|
class JWTManager {
|
|
49
13
|
constructor(config) {
|
|
50
14
|
this.accessSecret = config.accessSecret;
|
|
51
15
|
this.refreshSecret = config.refreshSecret;
|
|
52
|
-
this.accessExpiry = config.accessExpiry ||
|
|
53
|
-
this.refreshExpiry = config.refreshExpiry ||
|
|
16
|
+
this.accessExpiry = config.accessExpiry || '15m';
|
|
17
|
+
this.refreshExpiry = config.refreshExpiry || '7d';
|
|
18
|
+
this.cacheTTL = 5 * 60 * 1000; // 5 minutes default TTL
|
|
54
19
|
if (config.enableCaching) {
|
|
55
|
-
this.cache = new
|
|
20
|
+
this.cache = new js_extensions_1.LRUCache(config.maxCacheSize || 100);
|
|
56
21
|
}
|
|
57
22
|
}
|
|
58
23
|
/**
|
|
@@ -69,10 +34,11 @@ class JWTManager {
|
|
|
69
34
|
};
|
|
70
35
|
}
|
|
71
36
|
catch (error) {
|
|
72
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
37
|
+
if (error instanceof errors_utils_1.BadRequestError ||
|
|
38
|
+
error instanceof errors_utils_1.ValidationError) {
|
|
73
39
|
throw error;
|
|
74
40
|
}
|
|
75
|
-
throw new errors_utils_1.BadRequestError(
|
|
41
|
+
throw new errors_utils_1.BadRequestError('Failed to generate tokens');
|
|
76
42
|
}
|
|
77
43
|
}
|
|
78
44
|
/**
|
|
@@ -81,14 +47,17 @@ class JWTManager {
|
|
|
81
47
|
async generateAccessToken(payload) {
|
|
82
48
|
try {
|
|
83
49
|
this.validatePayload(payload);
|
|
84
|
-
const token = (0, signToken_1.signToken)(payload, this.accessSecret, this.accessExpiry, {
|
|
50
|
+
const token = (0, signToken_1.signToken)(payload, this.accessSecret, this.accessExpiry, {
|
|
51
|
+
algorithm: 'HS256',
|
|
52
|
+
});
|
|
85
53
|
return token;
|
|
86
54
|
}
|
|
87
55
|
catch (error) {
|
|
88
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
56
|
+
if (error instanceof errors_utils_1.BadRequestError ||
|
|
57
|
+
error instanceof errors_utils_1.ValidationError) {
|
|
89
58
|
throw error;
|
|
90
59
|
}
|
|
91
|
-
throw new errors_utils_1.BadRequestError(
|
|
60
|
+
throw new errors_utils_1.BadRequestError('Failed to generate access token');
|
|
92
61
|
}
|
|
93
62
|
}
|
|
94
63
|
/**
|
|
@@ -97,14 +66,17 @@ class JWTManager {
|
|
|
97
66
|
async generateRefreshToken(payload) {
|
|
98
67
|
try {
|
|
99
68
|
this.validatePayload(payload);
|
|
100
|
-
const token = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry, {
|
|
69
|
+
const token = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry, {
|
|
70
|
+
algorithm: 'HS256',
|
|
71
|
+
});
|
|
101
72
|
return token;
|
|
102
73
|
}
|
|
103
74
|
catch (error) {
|
|
104
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
75
|
+
if (error instanceof errors_utils_1.BadRequestError ||
|
|
76
|
+
error instanceof errors_utils_1.ValidationError) {
|
|
105
77
|
throw error;
|
|
106
78
|
}
|
|
107
|
-
throw new errors_utils_1.BadRequestError(
|
|
79
|
+
throw new errors_utils_1.BadRequestError('Failed to generate refresh token');
|
|
108
80
|
}
|
|
109
81
|
}
|
|
110
82
|
/**
|
|
@@ -112,36 +84,41 @@ class JWTManager {
|
|
|
112
84
|
*/
|
|
113
85
|
async verifyAccessToken(token) {
|
|
114
86
|
try {
|
|
115
|
-
if (!token || typeof token !==
|
|
116
|
-
throw new errors_utils_1.ValidationError(
|
|
87
|
+
if (!token || typeof token !== 'string') {
|
|
88
|
+
throw new errors_utils_1.ValidationError('Access token must be a non-empty string');
|
|
117
89
|
}
|
|
118
90
|
const cacheKey = `access_${token}`;
|
|
119
91
|
if (this.cache) {
|
|
120
92
|
const cached = this.cache.get(cacheKey);
|
|
121
|
-
if (cached) {
|
|
93
|
+
if (cached && Date.now() - cached.timestamp <= this.cacheTTL) {
|
|
122
94
|
if (!cached.valid) {
|
|
123
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
95
|
+
throw new errors_utils_1.UnauthorizedError('Access token is invalid or expired');
|
|
124
96
|
}
|
|
125
97
|
return cached.payload;
|
|
126
98
|
}
|
|
127
99
|
}
|
|
128
100
|
const decoded = (0, verify_1.verifyToken)(token, this.accessSecret);
|
|
129
101
|
if (this.cache) {
|
|
130
|
-
this.cache.set(cacheKey, {
|
|
102
|
+
this.cache.set(cacheKey, {
|
|
103
|
+
valid: true,
|
|
104
|
+
payload: decoded,
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
});
|
|
131
107
|
}
|
|
132
108
|
return decoded;
|
|
133
109
|
}
|
|
134
110
|
catch (error) {
|
|
135
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
111
|
+
if (error instanceof errors_utils_1.ValidationError ||
|
|
112
|
+
error instanceof errors_utils_1.UnauthorizedError) {
|
|
136
113
|
throw error;
|
|
137
114
|
}
|
|
138
|
-
if (error instanceof Error && error.name ===
|
|
139
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
115
|
+
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
116
|
+
throw new errors_utils_1.UnauthorizedError('Access token has expired');
|
|
140
117
|
}
|
|
141
|
-
if (error instanceof Error && error.name ===
|
|
142
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
118
|
+
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
119
|
+
throw new errors_utils_1.UnauthorizedError('Access token is invalid');
|
|
143
120
|
}
|
|
144
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
121
|
+
throw new errors_utils_1.UnauthorizedError('Failed to verify access token');
|
|
145
122
|
}
|
|
146
123
|
}
|
|
147
124
|
/**
|
|
@@ -149,36 +126,37 @@ class JWTManager {
|
|
|
149
126
|
*/
|
|
150
127
|
async verifyRefreshToken(token) {
|
|
151
128
|
try {
|
|
152
|
-
if (!token || typeof token !==
|
|
153
|
-
throw new errors_utils_1.ValidationError(
|
|
129
|
+
if (!token || typeof token !== 'string') {
|
|
130
|
+
throw new errors_utils_1.ValidationError('Refresh token must be a non-empty string');
|
|
154
131
|
}
|
|
155
132
|
const cacheKey = `refresh_${token}`;
|
|
156
133
|
if (this.cache) {
|
|
157
134
|
const cached = this.cache.get(cacheKey);
|
|
158
135
|
if (cached) {
|
|
159
136
|
if (!cached.valid) {
|
|
160
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
137
|
+
throw new errors_utils_1.UnauthorizedError('Refresh token is invalid or expired');
|
|
161
138
|
}
|
|
162
139
|
return cached.payload;
|
|
163
140
|
}
|
|
164
141
|
}
|
|
165
142
|
const decoded = (0, verify_1.verifyToken)(token, this.refreshSecret);
|
|
166
143
|
if (this.cache) {
|
|
167
|
-
this.cache.set(cacheKey, { valid: true, payload: decoded });
|
|
144
|
+
this.cache.set(cacheKey, { valid: true, payload: decoded, timestamp: Date.now() });
|
|
168
145
|
}
|
|
169
146
|
return decoded;
|
|
170
147
|
}
|
|
171
148
|
catch (error) {
|
|
172
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
149
|
+
if (error instanceof errors_utils_1.ValidationError ||
|
|
150
|
+
error instanceof errors_utils_1.UnauthorizedError) {
|
|
173
151
|
throw error;
|
|
174
152
|
}
|
|
175
|
-
if (error instanceof Error && error.name ===
|
|
176
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
153
|
+
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
154
|
+
throw new errors_utils_1.UnauthorizedError('Refresh token has expired');
|
|
177
155
|
}
|
|
178
|
-
if (error instanceof Error && error.name ===
|
|
179
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
156
|
+
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
157
|
+
throw new errors_utils_1.UnauthorizedError('Refresh token is invalid');
|
|
180
158
|
}
|
|
181
|
-
throw new errors_utils_1.UnauthorizedError(
|
|
159
|
+
throw new errors_utils_1.UnauthorizedError('Failed to verify refresh token');
|
|
182
160
|
}
|
|
183
161
|
}
|
|
184
162
|
/**
|
|
@@ -186,8 +164,8 @@ class JWTManager {
|
|
|
186
164
|
*/
|
|
187
165
|
decodeToken(token, complete = false) {
|
|
188
166
|
try {
|
|
189
|
-
if (!token || typeof token !==
|
|
190
|
-
throw new errors_utils_1.ValidationError(
|
|
167
|
+
if (!token || typeof token !== 'string') {
|
|
168
|
+
throw new errors_utils_1.ValidationError('Token must be a non-empty string');
|
|
191
169
|
}
|
|
192
170
|
return jsonwebtoken_1.default.decode(token, { complete });
|
|
193
171
|
}
|
|
@@ -203,11 +181,11 @@ class JWTManager {
|
|
|
203
181
|
*/
|
|
204
182
|
extractTokenFromHeader(authHeader) {
|
|
205
183
|
try {
|
|
206
|
-
if (!authHeader || typeof authHeader !==
|
|
184
|
+
if (!authHeader || typeof authHeader !== 'string') {
|
|
207
185
|
return null;
|
|
208
186
|
}
|
|
209
|
-
const parts = authHeader.split(
|
|
210
|
-
if (parts.length !== 2 || parts[0] !==
|
|
187
|
+
const parts = authHeader.split(' ');
|
|
188
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
211
189
|
return null;
|
|
212
190
|
}
|
|
213
191
|
return parts[1];
|
|
@@ -221,7 +199,7 @@ class JWTManager {
|
|
|
221
199
|
*/
|
|
222
200
|
validateToken(token, secret, options = {}) {
|
|
223
201
|
try {
|
|
224
|
-
if (!token || typeof token !==
|
|
202
|
+
if (!token || typeof token !== 'string') {
|
|
225
203
|
return false;
|
|
226
204
|
}
|
|
227
205
|
const result = (0, verify_1.safeVerifyToken)(token, secret);
|
|
@@ -236,12 +214,12 @@ class JWTManager {
|
|
|
236
214
|
*/
|
|
237
215
|
async rotateRefreshToken(oldToken) {
|
|
238
216
|
try {
|
|
239
|
-
if (!oldToken || typeof oldToken !==
|
|
240
|
-
throw new errors_utils_1.ValidationError(
|
|
217
|
+
if (!oldToken || typeof oldToken !== 'string') {
|
|
218
|
+
throw new errors_utils_1.ValidationError('Old refresh token must be a non-empty string');
|
|
241
219
|
}
|
|
242
220
|
const decoded = await this.verifyRefreshToken(oldToken);
|
|
243
|
-
if (typeof decoded ===
|
|
244
|
-
throw new errors_utils_1.ValidationError(
|
|
221
|
+
if (typeof decoded === 'string') {
|
|
222
|
+
throw new errors_utils_1.ValidationError('Invalid token payload — expected JWT payload object');
|
|
245
223
|
}
|
|
246
224
|
// Create new payload without issued/expired timestamps
|
|
247
225
|
const payload = { ...decoded };
|
|
@@ -252,10 +230,11 @@ class JWTManager {
|
|
|
252
230
|
return newToken;
|
|
253
231
|
}
|
|
254
232
|
catch (error) {
|
|
255
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
233
|
+
if (error instanceof errors_utils_1.ValidationError ||
|
|
234
|
+
error instanceof errors_utils_1.UnauthorizedError) {
|
|
256
235
|
throw error;
|
|
257
236
|
}
|
|
258
|
-
throw new errors_utils_1.BadRequestError(
|
|
237
|
+
throw new errors_utils_1.BadRequestError('Failed to rotate refresh token');
|
|
259
238
|
}
|
|
260
239
|
}
|
|
261
240
|
/**
|
|
@@ -301,18 +280,19 @@ class JWTManager {
|
|
|
301
280
|
getCacheStats() {
|
|
302
281
|
if (!this.cache)
|
|
303
282
|
return null;
|
|
283
|
+
// Note: LRUCache doesn't expose internal size, so we return maxSize only
|
|
304
284
|
return {
|
|
305
|
-
size:
|
|
306
|
-
maxSize: this.cache.maxSize
|
|
285
|
+
size: -1, // Size not available from LRUCache
|
|
286
|
+
maxSize: this.cache.maxSize,
|
|
307
287
|
};
|
|
308
288
|
}
|
|
309
289
|
// Private helper methods
|
|
310
290
|
validatePayload(payload) {
|
|
311
|
-
if (!payload || typeof payload !==
|
|
312
|
-
throw new errors_utils_1.ValidationError(
|
|
291
|
+
if (!payload || typeof payload !== 'object') {
|
|
292
|
+
throw new errors_utils_1.ValidationError('Payload must be a non-null object');
|
|
313
293
|
}
|
|
314
294
|
if (Object.keys(payload).length === 0) {
|
|
315
|
-
throw new errors_utils_1.ValidationError(
|
|
295
|
+
throw new errors_utils_1.ValidationError('Payload cannot be empty');
|
|
316
296
|
}
|
|
317
297
|
}
|
|
318
298
|
}
|
|
@@ -6,16 +6,16 @@ const TIME_UNITS = {
|
|
|
6
6
|
m: 60,
|
|
7
7
|
h: 3600,
|
|
8
8
|
d: 86400,
|
|
9
|
-
w: 604800
|
|
9
|
+
w: 604800,
|
|
10
10
|
};
|
|
11
11
|
function parseDuration(input) {
|
|
12
|
-
if (typeof input ===
|
|
12
|
+
if (typeof input === 'number')
|
|
13
13
|
return input;
|
|
14
14
|
const regex = /(\d+)\s*(s|m|h|d|w)/gi;
|
|
15
15
|
let totalSeconds = 0;
|
|
16
16
|
let match;
|
|
17
17
|
while ((match = regex.exec(input)) !== null) {
|
|
18
|
-
const value = parseInt(match[1], 10);
|
|
18
|
+
const value = Number.parseInt(match[1], 10);
|
|
19
19
|
const unit = match[2].toLowerCase();
|
|
20
20
|
if (!TIME_UNITS[unit]) {
|
|
21
21
|
throw new Error(`Invalid time unit: ${unit}`);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Secret, SignOptions } from
|
|
1
|
+
import { type Secret, type SignOptions } from 'jsonwebtoken';
|
|
2
2
|
export declare const signToken: (payload: Record<string, unknown>, secret: Secret, expiresIn?: string | number, options?: SignOptions) => string;
|
|
@@ -6,21 +6,21 @@ const parseDuration_1 = require("./parseDuration");
|
|
|
6
6
|
function getExpiryTimestamp(seconds) {
|
|
7
7
|
return Math.floor(Date.now() / 1000) + seconds;
|
|
8
8
|
}
|
|
9
|
-
const signToken = (payload, secret, expiresIn =
|
|
9
|
+
const signToken = (payload, secret, expiresIn = '1h', options = {}) => {
|
|
10
10
|
const seconds = (0, parseDuration_1.parseDuration)(expiresIn);
|
|
11
11
|
if (!seconds || seconds < 10) {
|
|
12
|
-
throw new Error(
|
|
12
|
+
throw new Error('Token expiry too small');
|
|
13
13
|
}
|
|
14
14
|
const tokenPayload = {
|
|
15
|
-
...payload
|
|
15
|
+
...payload,
|
|
16
16
|
};
|
|
17
|
-
if (!(
|
|
17
|
+
if (!('exp' in payload))
|
|
18
18
|
tokenPayload.exp = getExpiryTimestamp(seconds);
|
|
19
|
-
if (!(
|
|
19
|
+
if (!('iat' in payload))
|
|
20
20
|
tokenPayload.iat = Math.floor(Date.now() / 1000);
|
|
21
21
|
return (0, jsonwebtoken_1.sign)(tokenPayload, secret, {
|
|
22
|
-
algorithm:
|
|
23
|
-
...options
|
|
22
|
+
algorithm: 'HS256',
|
|
23
|
+
...options,
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
26
|
exports.signToken = signToken;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { JwtPayload } from
|
|
1
|
+
import type { JwtPayload } from 'node_modules/@types/jsonwebtoken';
|
|
2
2
|
export interface TokenRequirements {
|
|
3
3
|
requiredFields?: string[];
|
|
4
4
|
forbiddenFields?: string[];
|
|
5
|
-
validateTypes?: Record<string,
|
|
5
|
+
validateTypes?: Record<string, 'string' | 'number' | 'boolean'>;
|
|
6
6
|
}
|
|
7
7
|
export declare function validateTokenPayload(payload: Record<string, unknown>, rules?: TokenRequirements): {
|
|
8
8
|
valid: true;
|