@naman_deep_singh/security 1.3.3 → 1.4.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 +153 -355
- package/dist/cjs/core/crypto/cryptoManager.js +34 -17
- package/dist/cjs/core/jwt/decode.js +4 -1
- package/dist/cjs/core/jwt/generateTokens.js +4 -1
- package/dist/cjs/core/jwt/jwtManager.d.ts +19 -43
- package/dist/cjs/core/jwt/jwtManager.js +72 -206
- package/dist/cjs/core/jwt/parseDuration.js +3 -2
- package/dist/cjs/core/jwt/signToken.js +2 -1
- package/dist/cjs/core/jwt/validateToken.d.ts +10 -7
- package/dist/cjs/core/jwt/validateToken.js +14 -11
- package/dist/cjs/core/jwt/verify.d.ts +9 -10
- package/dist/cjs/core/jwt/verify.js +57 -14
- package/dist/cjs/core/password/hash.js +2 -2
- package/dist/cjs/core/password/passwordManager.d.ts +1 -1
- package/dist/cjs/core/password/passwordManager.js +35 -87
- package/dist/cjs/core/password/strength.js +5 -5
- package/dist/cjs/core/password/utils.d.ts +12 -0
- package/dist/cjs/core/password/utils.js +16 -1
- package/dist/cjs/core/password/verify.js +4 -4
- package/dist/cjs/index.d.ts +2 -7
- package/dist/esm/core/crypto/cryptoManager.js +34 -17
- package/dist/esm/core/jwt/decode.js +4 -1
- package/dist/esm/core/jwt/generateTokens.js +4 -1
- package/dist/esm/core/jwt/jwtManager.d.ts +19 -43
- package/dist/esm/core/jwt/jwtManager.js +73 -207
- package/dist/esm/core/jwt/parseDuration.js +3 -2
- package/dist/esm/core/jwt/signToken.js +2 -1
- package/dist/esm/core/jwt/validateToken.d.ts +10 -7
- package/dist/esm/core/jwt/validateToken.js +14 -11
- package/dist/esm/core/jwt/verify.d.ts +9 -10
- package/dist/esm/core/jwt/verify.js +55 -12
- package/dist/esm/core/password/hash.js +2 -2
- package/dist/esm/core/password/passwordManager.d.ts +1 -1
- package/dist/esm/core/password/passwordManager.js +35 -87
- package/dist/esm/core/password/strength.js +5 -5
- package/dist/esm/core/password/utils.d.ts +12 -0
- package/dist/esm/core/password/utils.js +16 -1
- package/dist/esm/core/password/verify.js +4 -4
- package/dist/esm/index.d.ts +2 -7
- package/dist/types/core/jwt/jwtManager.d.ts +19 -43
- package/dist/types/core/jwt/validateToken.d.ts +10 -7
- package/dist/types/core/jwt/verify.d.ts +9 -10
- package/dist/types/core/password/passwordManager.d.ts +1 -1
- package/dist/types/core/password/utils.d.ts +12 -0
- package/dist/types/index.d.ts +2 -7
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cryptoManager = exports.createCryptoManager = exports.CryptoManager = void 0;
|
|
4
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
4
5
|
const index_1 = require("./index");
|
|
5
6
|
/**
|
|
6
7
|
* Default configuration
|
|
@@ -34,17 +35,27 @@ class CryptoManager {
|
|
|
34
35
|
* Encrypt data using the default or specified algorithm
|
|
35
36
|
*/
|
|
36
37
|
encrypt(plaintext, key, _options) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
try {
|
|
39
|
+
return (0, index_1.encrypt)(plaintext, key);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new errors_utils_1.InternalServerError(undefined, {
|
|
43
|
+
message: 'Encryption failed',
|
|
44
|
+
}, err instanceof Error ? err : undefined);
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
/**
|
|
42
48
|
* Decrypt data using the default or specified algorithm
|
|
43
49
|
*/
|
|
44
50
|
decrypt(encryptedData, key, _options) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
try {
|
|
52
|
+
return (0, index_1.decrypt)(encryptedData, key);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw new errors_utils_1.InternalServerError(undefined, {
|
|
56
|
+
message: 'Decryption failed',
|
|
57
|
+
}, err instanceof Error ? err : undefined);
|
|
58
|
+
}
|
|
48
59
|
}
|
|
49
60
|
/**
|
|
50
61
|
* Generate HMAC signature
|
|
@@ -76,7 +87,9 @@ class CryptoManager {
|
|
|
76
87
|
const crypto = require('crypto');
|
|
77
88
|
crypto.pbkdf2(password, salt, iterations, keyLength, 'sha256', (err, derivedKey) => {
|
|
78
89
|
if (err) {
|
|
79
|
-
reject(
|
|
90
|
+
reject(new errors_utils_1.InternalServerError(undefined, {
|
|
91
|
+
message: 'Key derivation failed',
|
|
92
|
+
}, err instanceof Error ? err : undefined));
|
|
80
93
|
}
|
|
81
94
|
else {
|
|
82
95
|
resolve(derivedKey.toString('hex'));
|
|
@@ -146,15 +159,17 @@ class CryptoManager {
|
|
|
146
159
|
rsaSign(data, privateKey, algorithm = 'sha256') {
|
|
147
160
|
return new Promise((resolve, reject) => {
|
|
148
161
|
const crypto = require('crypto');
|
|
149
|
-
const sign = crypto.createSign(algorithm);
|
|
150
|
-
sign.update(data);
|
|
151
|
-
sign.end();
|
|
152
162
|
try {
|
|
163
|
+
const sign = crypto.createSign(algorithm);
|
|
164
|
+
sign.update(data);
|
|
165
|
+
sign.end();
|
|
153
166
|
const signature = sign.sign(privateKey, 'base64');
|
|
154
167
|
resolve(signature);
|
|
155
168
|
}
|
|
156
|
-
catch (
|
|
157
|
-
reject(
|
|
169
|
+
catch (err) {
|
|
170
|
+
reject(new errors_utils_1.InternalServerError(undefined, {
|
|
171
|
+
message: 'RSA signing failed',
|
|
172
|
+
}, err instanceof Error ? err : undefined));
|
|
158
173
|
}
|
|
159
174
|
});
|
|
160
175
|
}
|
|
@@ -164,15 +179,17 @@ class CryptoManager {
|
|
|
164
179
|
rsaVerify(data, signature, publicKey, algorithm = 'sha256') {
|
|
165
180
|
return new Promise((resolve, reject) => {
|
|
166
181
|
const crypto = require('crypto');
|
|
167
|
-
const verify = crypto.createVerify(algorithm);
|
|
168
|
-
verify.update(data);
|
|
169
|
-
verify.end();
|
|
170
182
|
try {
|
|
183
|
+
const verify = crypto.createVerify(algorithm);
|
|
184
|
+
verify.update(data);
|
|
185
|
+
verify.end();
|
|
171
186
|
const isValid = verify.verify(publicKey, signature, 'base64');
|
|
172
187
|
resolve(isValid);
|
|
173
188
|
}
|
|
174
|
-
catch (
|
|
175
|
-
reject(
|
|
189
|
+
catch (err) {
|
|
190
|
+
reject(new errors_utils_1.InternalServerError(undefined, {
|
|
191
|
+
message: 'RSA verification failed',
|
|
192
|
+
}, err instanceof Error ? err : undefined));
|
|
176
193
|
}
|
|
177
194
|
});
|
|
178
195
|
}
|
|
@@ -4,6 +4,7 @@ exports.decodeToken = decodeToken;
|
|
|
4
4
|
exports.decodeTokenStrict = decodeTokenStrict;
|
|
5
5
|
// src/jwt/decodeToken.ts
|
|
6
6
|
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
7
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
7
8
|
/**
|
|
8
9
|
* Flexible decode
|
|
9
10
|
* Returns: null | string | JwtPayload
|
|
@@ -19,7 +20,9 @@ function decodeToken(token) {
|
|
|
19
20
|
function decodeTokenStrict(token) {
|
|
20
21
|
const decoded = (0, jsonwebtoken_1.decode)(token);
|
|
21
22
|
if (!decoded || typeof decoded === 'string') {
|
|
22
|
-
throw new
|
|
23
|
+
throw new errors_utils_1.BadRequestError({
|
|
24
|
+
message: 'Invalid JWT payload structure',
|
|
25
|
+
});
|
|
23
26
|
}
|
|
24
27
|
return decoded;
|
|
25
28
|
}
|
|
@@ -4,6 +4,7 @@ exports.generateTokens = void 0;
|
|
|
4
4
|
exports.rotateRefreshToken = rotateRefreshToken;
|
|
5
5
|
const signToken_1 = require("./signToken");
|
|
6
6
|
const verify_1 = require("./verify");
|
|
7
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
7
8
|
// Helper function to create branded tokens
|
|
8
9
|
/* const createBrandedToken = <T extends string>(token: string, _brand: T): T => {
|
|
9
10
|
return token as T
|
|
@@ -24,7 +25,9 @@ exports.generateTokens = generateTokens;
|
|
|
24
25
|
function rotateRefreshToken(oldToken, secret) {
|
|
25
26
|
const decoded = (0, verify_1.verifyToken)(oldToken, secret);
|
|
26
27
|
if (typeof decoded === 'string') {
|
|
27
|
-
throw new
|
|
28
|
+
throw new errors_utils_1.TokenMalformedError({
|
|
29
|
+
message: 'Invalid token payload — expected JWT payload object',
|
|
30
|
+
});
|
|
28
31
|
}
|
|
29
32
|
const payload = { ...decoded };
|
|
30
33
|
delete payload.iat;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type JwtPayload, type Secret } from 'jsonwebtoken';
|
|
2
|
-
import type { AccessToken, ITokenManager, JWTConfig, RefreshToken, TokenPair
|
|
2
|
+
import type { AccessToken, ITokenManager, JWTConfig, RefreshToken, TokenPair } from '../../interfaces/jwt.interface';
|
|
3
3
|
export declare class JWTManager implements ITokenManager {
|
|
4
4
|
private accessSecret;
|
|
5
5
|
private refreshSecret;
|
|
@@ -8,60 +8,36 @@ export declare class JWTManager implements ITokenManager {
|
|
|
8
8
|
private cache?;
|
|
9
9
|
private cacheTTL;
|
|
10
10
|
constructor(config: JWTConfig);
|
|
11
|
-
/**
|
|
12
|
-
* Generate both access and refresh tokens
|
|
13
|
-
*/
|
|
11
|
+
/** Generate both access and refresh tokens */
|
|
14
12
|
generateTokens(payload: Record<string, unknown>): Promise<TokenPair>;
|
|
15
|
-
/**
|
|
16
|
-
* Generate access token
|
|
17
|
-
*/
|
|
13
|
+
/** Generate access token */
|
|
18
14
|
generateAccessToken(payload: Record<string, unknown>): Promise<AccessToken>;
|
|
19
|
-
/**
|
|
20
|
-
* Generate refresh token
|
|
21
|
-
*/
|
|
15
|
+
/** Generate refresh token */
|
|
22
16
|
generateRefreshToken(payload: Record<string, unknown>): Promise<RefreshToken>;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Verify refresh token
|
|
29
|
-
*/
|
|
30
|
-
verifyRefreshToken(token: string): Promise<JwtPayload | string>;
|
|
31
|
-
/**
|
|
32
|
-
* Decode token without verification
|
|
33
|
-
*/
|
|
17
|
+
/** Verify access token */
|
|
18
|
+
verifyAccessToken(token: string): Promise<JwtPayload>;
|
|
19
|
+
/** Verify refresh token */
|
|
20
|
+
verifyRefreshToken(token: string): Promise<JwtPayload>;
|
|
21
|
+
/** Decode token without verification */
|
|
34
22
|
decodeToken(token: string, complete?: boolean): JwtPayload | string | null;
|
|
35
|
-
/**
|
|
36
|
-
* Extract token from Authorization header
|
|
37
|
-
*/
|
|
23
|
+
/** Extract token from Authorization header */
|
|
38
24
|
extractTokenFromHeader(authHeader: string): string | null;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
validateToken(token: string, secret: Secret, _options?: TokenValidationOptions): boolean;
|
|
43
|
-
/**
|
|
44
|
-
* Rotate refresh token
|
|
45
|
-
*/
|
|
25
|
+
/** Validate token without throwing exceptions */
|
|
26
|
+
validateToken(token: string, secret: Secret): boolean;
|
|
27
|
+
/** Rotate refresh token */
|
|
46
28
|
rotateRefreshToken(oldToken: string): Promise<RefreshToken>;
|
|
47
|
-
/**
|
|
48
|
-
* Check if token is expired
|
|
49
|
-
*/
|
|
29
|
+
/** Check if token is expired */
|
|
50
30
|
isTokenExpired(token: string): boolean;
|
|
51
|
-
/**
|
|
52
|
-
* Get token expiration date
|
|
53
|
-
*/
|
|
31
|
+
/** Get token expiration date */
|
|
54
32
|
getTokenExpiration(token: string): Date | null;
|
|
55
|
-
/**
|
|
56
|
-
* Clear token cache
|
|
57
|
-
*/
|
|
33
|
+
/** Clear token cache */
|
|
58
34
|
clearCache(): void;
|
|
59
|
-
/**
|
|
60
|
-
* Get cache statistics
|
|
61
|
-
*/
|
|
35
|
+
/** Get cache statistics */
|
|
62
36
|
getCacheStats(): {
|
|
63
37
|
size: number;
|
|
64
38
|
maxSize: number;
|
|
65
39
|
} | null;
|
|
40
|
+
/** Private helper methods */
|
|
66
41
|
private validatePayload;
|
|
42
|
+
private verifyTokenWithCache;
|
|
67
43
|
}
|
|
@@ -15,289 +15,155 @@ class JWTManager {
|
|
|
15
15
|
this.refreshSecret = config.refreshSecret;
|
|
16
16
|
this.accessExpiry = config.accessExpiry || '15m';
|
|
17
17
|
this.refreshExpiry = config.refreshExpiry || '7d';
|
|
18
|
-
this.cacheTTL = 5 * 60 * 1000; // 5 minutes
|
|
18
|
+
this.cacheTTL = 5 * 60 * 1000; // 5 minutes
|
|
19
19
|
if (config.enableCaching) {
|
|
20
20
|
this.cache = new js_extensions_1.LRUCache(config.maxCacheSize || 100);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Generate both access and refresh tokens
|
|
25
|
-
*/
|
|
23
|
+
/** Generate both access and refresh tokens */
|
|
26
24
|
async generateTokens(payload) {
|
|
27
25
|
try {
|
|
28
26
|
this.validatePayload(payload);
|
|
29
27
|
const accessToken = await this.generateAccessToken(payload);
|
|
30
28
|
const refreshToken = await this.generateRefreshToken(payload);
|
|
31
|
-
return {
|
|
32
|
-
accessToken,
|
|
33
|
-
refreshToken,
|
|
34
|
-
};
|
|
29
|
+
return { accessToken, refreshToken };
|
|
35
30
|
}
|
|
36
31
|
catch (error) {
|
|
37
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
38
|
-
error instanceof errors_utils_1.ValidationError) {
|
|
32
|
+
if (error instanceof errors_utils_1.BadRequestError || error instanceof errors_utils_1.ValidationError)
|
|
39
33
|
throw error;
|
|
40
|
-
}
|
|
41
|
-
throw new errors_utils_1.BadRequestError('Failed to generate tokens');
|
|
34
|
+
throw new errors_utils_1.BadRequestError({ message: 'Failed to generate tokens' }, error instanceof Error ? error : undefined);
|
|
42
35
|
}
|
|
43
36
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Generate access token
|
|
46
|
-
*/
|
|
37
|
+
/** Generate access token */
|
|
47
38
|
async generateAccessToken(payload) {
|
|
48
39
|
try {
|
|
49
40
|
this.validatePayload(payload);
|
|
50
|
-
const token = (0, signToken_1.signToken)(payload, this.accessSecret, this.accessExpiry, {
|
|
51
|
-
algorithm: 'HS256',
|
|
52
|
-
});
|
|
41
|
+
const token = (0, signToken_1.signToken)(payload, this.accessSecret, this.accessExpiry, { algorithm: 'HS256' });
|
|
53
42
|
return token;
|
|
54
43
|
}
|
|
55
44
|
catch (error) {
|
|
56
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
57
|
-
error instanceof errors_utils_1.ValidationError) {
|
|
45
|
+
if (error instanceof errors_utils_1.BadRequestError || error instanceof errors_utils_1.ValidationError)
|
|
58
46
|
throw error;
|
|
59
|
-
}
|
|
60
|
-
throw new errors_utils_1.BadRequestError('Failed to generate access token');
|
|
47
|
+
throw new errors_utils_1.BadRequestError({ message: 'Failed to generate access token' }, error instanceof Error ? error : undefined);
|
|
61
48
|
}
|
|
62
49
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Generate refresh token
|
|
65
|
-
*/
|
|
50
|
+
/** Generate refresh token */
|
|
66
51
|
async generateRefreshToken(payload) {
|
|
67
52
|
try {
|
|
68
53
|
this.validatePayload(payload);
|
|
69
|
-
const token = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry, {
|
|
70
|
-
algorithm: 'HS256',
|
|
71
|
-
});
|
|
54
|
+
const token = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry, { algorithm: 'HS256' });
|
|
72
55
|
return token;
|
|
73
56
|
}
|
|
74
57
|
catch (error) {
|
|
75
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
76
|
-
error instanceof errors_utils_1.ValidationError) {
|
|
58
|
+
if (error instanceof errors_utils_1.BadRequestError || error instanceof errors_utils_1.ValidationError)
|
|
77
59
|
throw error;
|
|
78
|
-
}
|
|
79
|
-
throw new errors_utils_1.BadRequestError('Failed to generate refresh token');
|
|
60
|
+
throw new errors_utils_1.BadRequestError({ message: 'Failed to generate refresh token' }, error instanceof Error ? error : undefined);
|
|
80
61
|
}
|
|
81
62
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Verify access token
|
|
84
|
-
*/
|
|
63
|
+
/** Verify access token */
|
|
85
64
|
async verifyAccessToken(token) {
|
|
86
|
-
|
|
87
|
-
if (!token || typeof token !== 'string') {
|
|
88
|
-
throw new errors_utils_1.ValidationError('Access token must be a non-empty string');
|
|
89
|
-
}
|
|
90
|
-
const cacheKey = `access_${token}`;
|
|
91
|
-
if (this.cache) {
|
|
92
|
-
const cached = this.cache.get(cacheKey);
|
|
93
|
-
if (cached && Date.now() - cached.timestamp <= this.cacheTTL) {
|
|
94
|
-
if (!cached.valid) {
|
|
95
|
-
throw new errors_utils_1.UnauthorizedError('Access token is invalid or expired');
|
|
96
|
-
}
|
|
97
|
-
return cached.payload;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const decoded = (0, verify_1.verifyToken)(token, this.accessSecret);
|
|
101
|
-
if (this.cache) {
|
|
102
|
-
this.cache.set(cacheKey, {
|
|
103
|
-
valid: true,
|
|
104
|
-
payload: decoded,
|
|
105
|
-
timestamp: Date.now(),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
return decoded;
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
112
|
-
error instanceof errors_utils_1.UnauthorizedError) {
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
116
|
-
throw new errors_utils_1.UnauthorizedError('Access token has expired');
|
|
117
|
-
}
|
|
118
|
-
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
119
|
-
throw new errors_utils_1.UnauthorizedError('Access token is invalid');
|
|
120
|
-
}
|
|
121
|
-
throw new errors_utils_1.UnauthorizedError('Failed to verify access token');
|
|
122
|
-
}
|
|
65
|
+
return this.verifyTokenWithCache(token, this.accessSecret, 'access');
|
|
123
66
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Verify refresh token
|
|
126
|
-
*/
|
|
67
|
+
/** Verify refresh token */
|
|
127
68
|
async verifyRefreshToken(token) {
|
|
128
|
-
|
|
129
|
-
if (!token || typeof token !== 'string') {
|
|
130
|
-
throw new errors_utils_1.ValidationError('Refresh token must be a non-empty string');
|
|
131
|
-
}
|
|
132
|
-
const cacheKey = `refresh_${token}`;
|
|
133
|
-
if (this.cache) {
|
|
134
|
-
const cached = this.cache.get(cacheKey);
|
|
135
|
-
if (cached) {
|
|
136
|
-
if (!cached.valid) {
|
|
137
|
-
throw new errors_utils_1.UnauthorizedError('Refresh token is invalid or expired');
|
|
138
|
-
}
|
|
139
|
-
return cached.payload;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const decoded = (0, verify_1.verifyToken)(token, this.refreshSecret);
|
|
143
|
-
if (this.cache) {
|
|
144
|
-
this.cache.set(cacheKey, {
|
|
145
|
-
valid: true,
|
|
146
|
-
payload: decoded,
|
|
147
|
-
timestamp: Date.now(),
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
return decoded;
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
154
|
-
error instanceof errors_utils_1.UnauthorizedError) {
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
158
|
-
throw new errors_utils_1.UnauthorizedError('Refresh token has expired');
|
|
159
|
-
}
|
|
160
|
-
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
161
|
-
throw new errors_utils_1.UnauthorizedError('Refresh token is invalid');
|
|
162
|
-
}
|
|
163
|
-
throw new errors_utils_1.UnauthorizedError('Failed to verify refresh token');
|
|
164
|
-
}
|
|
69
|
+
return this.verifyTokenWithCache(token, this.refreshSecret, 'refresh');
|
|
165
70
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Decode token without verification
|
|
168
|
-
*/
|
|
71
|
+
/** Decode token without verification */
|
|
169
72
|
decodeToken(token, complete = false) {
|
|
170
|
-
|
|
171
|
-
if (!token || typeof token !== 'string') {
|
|
172
|
-
throw new errors_utils_1.ValidationError('Token must be a non-empty string');
|
|
173
|
-
}
|
|
174
|
-
return jsonwebtoken_1.default.decode(token, { complete });
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
if (error instanceof errors_utils_1.ValidationError) {
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
73
|
+
if (!token || typeof token !== 'string')
|
|
180
74
|
return null;
|
|
181
|
-
}
|
|
75
|
+
return jsonwebtoken_1.default.decode(token, { complete });
|
|
182
76
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Extract token from Authorization header
|
|
185
|
-
*/
|
|
77
|
+
/** Extract token from Authorization header */
|
|
186
78
|
extractTokenFromHeader(authHeader) {
|
|
187
|
-
|
|
188
|
-
if (!authHeader || typeof authHeader !== 'string') {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
const parts = authHeader.split(' ');
|
|
192
|
-
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
return parts[1];
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
79
|
+
if (!authHeader || typeof authHeader !== 'string')
|
|
198
80
|
return null;
|
|
199
|
-
|
|
81
|
+
const parts = authHeader.split(' ');
|
|
82
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer')
|
|
83
|
+
return null;
|
|
84
|
+
return parts[1];
|
|
200
85
|
}
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
validateToken(token, secret, _options = {}) {
|
|
205
|
-
try {
|
|
206
|
-
if (!token || typeof token !== 'string') {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
const result = (0, verify_1.safeVerifyToken)(token, secret);
|
|
210
|
-
return result.valid;
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
86
|
+
/** Validate token without throwing exceptions */
|
|
87
|
+
validateToken(token, secret) {
|
|
88
|
+
if (!token || typeof token !== 'string')
|
|
213
89
|
return false;
|
|
214
|
-
|
|
90
|
+
return (0, verify_1.safeVerifyToken)(token, secret).valid;
|
|
215
91
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Rotate refresh token
|
|
218
|
-
*/
|
|
92
|
+
/** Rotate refresh token */
|
|
219
93
|
async rotateRefreshToken(oldToken) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const payload = { ...decoded };
|
|
230
|
-
delete payload.iat;
|
|
231
|
-
delete payload.exp;
|
|
232
|
-
// Generate new refresh token
|
|
233
|
-
const newToken = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry);
|
|
234
|
-
return newToken;
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
238
|
-
error instanceof errors_utils_1.UnauthorizedError) {
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
throw new errors_utils_1.BadRequestError('Failed to rotate refresh token');
|
|
242
|
-
}
|
|
94
|
+
if (!oldToken || typeof oldToken !== 'string') {
|
|
95
|
+
throw new errors_utils_1.ValidationError({ message: 'Old refresh token must be a non-empty string' });
|
|
96
|
+
}
|
|
97
|
+
const decoded = await this.verifyRefreshToken(oldToken);
|
|
98
|
+
const payload = { ...decoded };
|
|
99
|
+
delete payload.iat;
|
|
100
|
+
delete payload.exp;
|
|
101
|
+
const newToken = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry);
|
|
102
|
+
return newToken;
|
|
243
103
|
}
|
|
244
|
-
/**
|
|
245
|
-
* Check if token is expired
|
|
246
|
-
*/
|
|
104
|
+
/** Check if token is expired */
|
|
247
105
|
isTokenExpired(token) {
|
|
248
106
|
try {
|
|
249
107
|
const decoded = this.decodeToken(token);
|
|
250
|
-
if (!decoded || !decoded.exp)
|
|
108
|
+
if (!decoded || !decoded.exp)
|
|
251
109
|
return true;
|
|
252
|
-
|
|
253
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
254
|
-
return decoded.exp < currentTime;
|
|
110
|
+
return decoded.exp < Math.floor(Date.now() / 1000);
|
|
255
111
|
}
|
|
256
112
|
catch {
|
|
257
113
|
return true;
|
|
258
114
|
}
|
|
259
115
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Get token expiration date
|
|
262
|
-
*/
|
|
116
|
+
/** Get token expiration date */
|
|
263
117
|
getTokenExpiration(token) {
|
|
264
118
|
try {
|
|
265
119
|
const decoded = this.decodeToken(token);
|
|
266
|
-
if (!decoded || !decoded.exp)
|
|
120
|
+
if (!decoded || !decoded.exp)
|
|
267
121
|
return null;
|
|
268
|
-
}
|
|
269
122
|
return new Date(decoded.exp * 1000);
|
|
270
123
|
}
|
|
271
124
|
catch {
|
|
272
125
|
return null;
|
|
273
126
|
}
|
|
274
127
|
}
|
|
275
|
-
/**
|
|
276
|
-
* Clear token cache
|
|
277
|
-
*/
|
|
128
|
+
/** Clear token cache */
|
|
278
129
|
clearCache() {
|
|
279
130
|
this.cache?.clear();
|
|
280
131
|
}
|
|
281
|
-
/**
|
|
282
|
-
* Get cache statistics
|
|
283
|
-
*/
|
|
132
|
+
/** Get cache statistics */
|
|
284
133
|
getCacheStats() {
|
|
285
134
|
if (!this.cache)
|
|
286
135
|
return null;
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
size: -1, // Size not available from LRUCache
|
|
290
|
-
maxSize: this.cache.maxSize,
|
|
291
|
-
};
|
|
136
|
+
return { size: -1, maxSize: this.cache.maxSize };
|
|
292
137
|
}
|
|
293
|
-
|
|
138
|
+
/** Private helper methods */
|
|
294
139
|
validatePayload(payload) {
|
|
295
140
|
if (!payload || typeof payload !== 'object') {
|
|
296
|
-
throw new errors_utils_1.ValidationError('Payload must be a non-null object');
|
|
141
|
+
throw new errors_utils_1.ValidationError({ message: 'Payload must be a non-null object' });
|
|
297
142
|
}
|
|
298
143
|
if (Object.keys(payload).length === 0) {
|
|
299
|
-
throw new errors_utils_1.ValidationError('Payload cannot be empty');
|
|
144
|
+
throw new errors_utils_1.ValidationError({ message: 'Payload cannot be empty' });
|
|
300
145
|
}
|
|
301
146
|
}
|
|
147
|
+
async verifyTokenWithCache(token, secret, type) {
|
|
148
|
+
if (!token || typeof token !== 'string') {
|
|
149
|
+
throw new errors_utils_1.ValidationError({ message: `${type} token must be a non-empty string` });
|
|
150
|
+
}
|
|
151
|
+
const cacheKey = `${type}_${token}`;
|
|
152
|
+
if (this.cache) {
|
|
153
|
+
const cached = this.cache.get(cacheKey);
|
|
154
|
+
if (cached && Date.now() - cached.timestamp <= this.cacheTTL) {
|
|
155
|
+
if (!cached.valid)
|
|
156
|
+
throw new errors_utils_1.UnauthorizedError({ message: `${type} token is invalid or expired` });
|
|
157
|
+
return cached.payload;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const { valid, payload, error } = (0, verify_1.safeVerifyToken)(token, secret);
|
|
161
|
+
if (!valid || !payload || typeof payload === 'string') {
|
|
162
|
+
this.cache?.set(cacheKey, { valid: false, payload: {}, timestamp: Date.now() });
|
|
163
|
+
throw new errors_utils_1.UnauthorizedError({ message: `${type} token is invalid or expired`, cause: error });
|
|
164
|
+
}
|
|
165
|
+
this.cache?.set(cacheKey, { valid: true, payload, timestamp: Date.now() });
|
|
166
|
+
return payload;
|
|
167
|
+
}
|
|
302
168
|
}
|
|
303
169
|
exports.JWTManager = JWTManager;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseDuration = parseDuration;
|
|
4
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
4
5
|
const TIME_UNITS = {
|
|
5
6
|
s: 1,
|
|
6
7
|
m: 60,
|
|
@@ -18,12 +19,12 @@ function parseDuration(input) {
|
|
|
18
19
|
const value = Number.parseInt(match[1], 10);
|
|
19
20
|
const unit = match[2].toLowerCase();
|
|
20
21
|
if (!TIME_UNITS[unit]) {
|
|
21
|
-
throw new
|
|
22
|
+
throw new errors_utils_1.ValidationError({ message: `Invalid time unit: ${unit}` });
|
|
22
23
|
}
|
|
23
24
|
totalSeconds += value * TIME_UNITS[unit];
|
|
24
25
|
}
|
|
25
26
|
if (totalSeconds === 0) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new errors_utils_1.ValidationError({ message: `Invalid expiry format: "${input}"` });
|
|
27
28
|
}
|
|
28
29
|
return totalSeconds;
|
|
29
30
|
}
|
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.signToken = void 0;
|
|
4
4
|
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
5
5
|
const parseDuration_1 = require("./parseDuration");
|
|
6
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
6
7
|
function getExpiryTimestamp(seconds) {
|
|
7
8
|
return Math.floor(Date.now() / 1000) + seconds;
|
|
8
9
|
}
|
|
9
10
|
const signToken = (payload, secret, expiresIn = '1h', options = {}) => {
|
|
10
11
|
const seconds = (0, parseDuration_1.parseDuration)(expiresIn);
|
|
11
12
|
if (!seconds || seconds < 10) {
|
|
12
|
-
throw new
|
|
13
|
+
throw new errors_utils_1.ValidationError({ message: 'Token expiry too small' });
|
|
13
14
|
}
|
|
14
15
|
const tokenPayload = {
|
|
15
16
|
...payload,
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type { JwtPayload } from '
|
|
1
|
+
import type { JwtPayload } from 'jsonwebtoken';
|
|
2
2
|
export interface TokenRequirements {
|
|
3
3
|
requiredFields?: string[];
|
|
4
4
|
forbiddenFields?: string[];
|
|
5
5
|
validateTypes?: Record<string, 'string' | 'number' | 'boolean'>;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Validates a JWT payload according to the provided rules.
|
|
9
|
+
* Throws ValidationError if validation fails.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateTokenPayload(payload: Record<string, unknown>, rules?: TokenRequirements): void;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a JWT payload is expired.
|
|
14
|
+
* Returns true if expired or missing 'exp'.
|
|
15
|
+
*/
|
|
13
16
|
export declare function isTokenExpired(payload: JwtPayload): boolean;
|