@naman_deep_singh/security 1.3.2 → 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.d.ts +5 -5
- package/dist/cjs/core/crypto/cryptoManager.js +42 -25
- package/dist/cjs/core/jwt/decode.js +4 -1
- package/dist/cjs/core/jwt/generateTokens.d.ts +1 -1
- package/dist/cjs/core/jwt/generateTokens.js +7 -4
- package/dist/cjs/core/jwt/jwtManager.d.ts +19 -43
- package/dist/cjs/core/jwt/jwtManager.js +72 -202
- 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 +4 -4
- package/dist/cjs/core/password/passwordManager.d.ts +2 -2
- package/dist/cjs/core/password/passwordManager.js +43 -82
- 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 +5 -5
- package/dist/cjs/index.d.ts +2 -7
- package/dist/esm/core/crypto/cryptoManager.d.ts +5 -5
- package/dist/esm/core/crypto/cryptoManager.js +42 -25
- package/dist/esm/core/jwt/decode.js +4 -1
- package/dist/esm/core/jwt/generateTokens.d.ts +1 -1
- package/dist/esm/core/jwt/generateTokens.js +7 -4
- package/dist/esm/core/jwt/jwtManager.d.ts +19 -43
- package/dist/esm/core/jwt/jwtManager.js +73 -203
- 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 +4 -4
- package/dist/esm/core/password/passwordManager.d.ts +2 -2
- package/dist/esm/core/password/passwordManager.js +43 -82
- 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 +5 -5
- package/dist/esm/index.d.ts +2 -7
- package/dist/types/core/crypto/cryptoManager.d.ts +5 -5
- package/dist/types/core/jwt/generateTokens.d.ts +1 -1
- 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 +2 -2
- package/dist/types/core/password/utils.d.ts +12 -0
- package/dist/types/index.d.ts +2 -7
- package/package.json +2 -2
|
@@ -15,285 +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, { valid: true, payload: decoded, timestamp: Date.now() });
|
|
145
|
-
}
|
|
146
|
-
return decoded;
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
150
|
-
error instanceof errors_utils_1.UnauthorizedError) {
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
154
|
-
throw new errors_utils_1.UnauthorizedError('Refresh token has expired');
|
|
155
|
-
}
|
|
156
|
-
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
157
|
-
throw new errors_utils_1.UnauthorizedError('Refresh token is invalid');
|
|
158
|
-
}
|
|
159
|
-
throw new errors_utils_1.UnauthorizedError('Failed to verify refresh token');
|
|
160
|
-
}
|
|
69
|
+
return this.verifyTokenWithCache(token, this.refreshSecret, 'refresh');
|
|
161
70
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Decode token without verification
|
|
164
|
-
*/
|
|
71
|
+
/** Decode token without verification */
|
|
165
72
|
decodeToken(token, complete = false) {
|
|
166
|
-
|
|
167
|
-
if (!token || typeof token !== 'string') {
|
|
168
|
-
throw new errors_utils_1.ValidationError('Token must be a non-empty string');
|
|
169
|
-
}
|
|
170
|
-
return jsonwebtoken_1.default.decode(token, { complete });
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
if (error instanceof errors_utils_1.ValidationError) {
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
73
|
+
if (!token || typeof token !== 'string')
|
|
176
74
|
return null;
|
|
177
|
-
}
|
|
75
|
+
return jsonwebtoken_1.default.decode(token, { complete });
|
|
178
76
|
}
|
|
179
|
-
/**
|
|
180
|
-
* Extract token from Authorization header
|
|
181
|
-
*/
|
|
77
|
+
/** Extract token from Authorization header */
|
|
182
78
|
extractTokenFromHeader(authHeader) {
|
|
183
|
-
|
|
184
|
-
if (!authHeader || typeof authHeader !== 'string') {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
const parts = authHeader.split(' ');
|
|
188
|
-
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
return parts[1];
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
79
|
+
if (!authHeader || typeof authHeader !== 'string')
|
|
194
80
|
return null;
|
|
195
|
-
|
|
81
|
+
const parts = authHeader.split(' ');
|
|
82
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer')
|
|
83
|
+
return null;
|
|
84
|
+
return parts[1];
|
|
196
85
|
}
|
|
197
|
-
/**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
validateToken(token, secret, options = {}) {
|
|
201
|
-
try {
|
|
202
|
-
if (!token || typeof token !== 'string') {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
const result = (0, verify_1.safeVerifyToken)(token, secret);
|
|
206
|
-
return result.valid;
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
86
|
+
/** Validate token without throwing exceptions */
|
|
87
|
+
validateToken(token, secret) {
|
|
88
|
+
if (!token || typeof token !== 'string')
|
|
209
89
|
return false;
|
|
210
|
-
|
|
90
|
+
return (0, verify_1.safeVerifyToken)(token, secret).valid;
|
|
211
91
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Rotate refresh token
|
|
214
|
-
*/
|
|
92
|
+
/** Rotate refresh token */
|
|
215
93
|
async rotateRefreshToken(oldToken) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const payload = { ...decoded };
|
|
226
|
-
delete payload.iat;
|
|
227
|
-
delete payload.exp;
|
|
228
|
-
// Generate new refresh token
|
|
229
|
-
const newToken = (0, signToken_1.signToken)(payload, this.refreshSecret, this.refreshExpiry);
|
|
230
|
-
return newToken;
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
if (error instanceof errors_utils_1.ValidationError ||
|
|
234
|
-
error instanceof errors_utils_1.UnauthorizedError) {
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
throw new errors_utils_1.BadRequestError('Failed to rotate refresh token');
|
|
238
|
-
}
|
|
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;
|
|
239
103
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Check if token is expired
|
|
242
|
-
*/
|
|
104
|
+
/** Check if token is expired */
|
|
243
105
|
isTokenExpired(token) {
|
|
244
106
|
try {
|
|
245
107
|
const decoded = this.decodeToken(token);
|
|
246
|
-
if (!decoded || !decoded.exp)
|
|
108
|
+
if (!decoded || !decoded.exp)
|
|
247
109
|
return true;
|
|
248
|
-
|
|
249
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
250
|
-
return decoded.exp < currentTime;
|
|
110
|
+
return decoded.exp < Math.floor(Date.now() / 1000);
|
|
251
111
|
}
|
|
252
112
|
catch {
|
|
253
113
|
return true;
|
|
254
114
|
}
|
|
255
115
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Get token expiration date
|
|
258
|
-
*/
|
|
116
|
+
/** Get token expiration date */
|
|
259
117
|
getTokenExpiration(token) {
|
|
260
118
|
try {
|
|
261
119
|
const decoded = this.decodeToken(token);
|
|
262
|
-
if (!decoded || !decoded.exp)
|
|
120
|
+
if (!decoded || !decoded.exp)
|
|
263
121
|
return null;
|
|
264
|
-
}
|
|
265
122
|
return new Date(decoded.exp * 1000);
|
|
266
123
|
}
|
|
267
124
|
catch {
|
|
268
125
|
return null;
|
|
269
126
|
}
|
|
270
127
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Clear token cache
|
|
273
|
-
*/
|
|
128
|
+
/** Clear token cache */
|
|
274
129
|
clearCache() {
|
|
275
130
|
this.cache?.clear();
|
|
276
131
|
}
|
|
277
|
-
/**
|
|
278
|
-
* Get cache statistics
|
|
279
|
-
*/
|
|
132
|
+
/** Get cache statistics */
|
|
280
133
|
getCacheStats() {
|
|
281
134
|
if (!this.cache)
|
|
282
135
|
return null;
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
size: -1, // Size not available from LRUCache
|
|
286
|
-
maxSize: this.cache.maxSize,
|
|
287
|
-
};
|
|
136
|
+
return { size: -1, maxSize: this.cache.maxSize };
|
|
288
137
|
}
|
|
289
|
-
|
|
138
|
+
/** Private helper methods */
|
|
290
139
|
validatePayload(payload) {
|
|
291
140
|
if (!payload || typeof payload !== 'object') {
|
|
292
|
-
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' });
|
|
293
142
|
}
|
|
294
143
|
if (Object.keys(payload).length === 0) {
|
|
295
|
-
throw new errors_utils_1.ValidationError('Payload cannot be empty');
|
|
144
|
+
throw new errors_utils_1.ValidationError({ message: 'Payload cannot be empty' });
|
|
296
145
|
}
|
|
297
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
|
+
}
|
|
298
168
|
}
|
|
299
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;
|
|
@@ -2,34 +2,37 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateTokenPayload = validateTokenPayload;
|
|
4
4
|
exports.isTokenExpired = isTokenExpired;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
6
|
+
/**
|
|
7
|
+
* Validates a JWT payload according to the provided rules.
|
|
8
|
+
* Throws ValidationError if validation fails.
|
|
9
|
+
*/
|
|
10
|
+
function validateTokenPayload(payload, rules = { requiredFields: ['exp', 'iat'] }) {
|
|
11
|
+
const { requiredFields = [], forbiddenFields = [], validateTypes = {} } = rules;
|
|
9
12
|
// 1. Required fields
|
|
10
13
|
for (const field of requiredFields) {
|
|
11
14
|
if (!(field in payload)) {
|
|
12
|
-
|
|
15
|
+
throw new errors_utils_1.ValidationError(`Missing required field: ${field}`);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
// 2. Forbidden fields
|
|
16
19
|
for (const field of forbiddenFields) {
|
|
17
20
|
if (field in payload) {
|
|
18
|
-
|
|
21
|
+
throw new errors_utils_1.ValidationError(`Forbidden field in token: ${field}`);
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
// 3. Type validation
|
|
22
25
|
for (const key in validateTypes) {
|
|
23
26
|
const expectedType = validateTypes[key];
|
|
24
27
|
if (key in payload && typeof payload[key] !== expectedType) {
|
|
25
|
-
|
|
26
|
-
valid: false,
|
|
27
|
-
error: `Invalid type for ${key}. Expected ${expectedType}.`,
|
|
28
|
-
};
|
|
28
|
+
throw new errors_utils_1.ValidationError(`Invalid type for ${key}. Expected ${expectedType}, got ${typeof payload[key]}`);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
return { valid: true };
|
|
32
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Checks if a JWT payload is expired.
|
|
34
|
+
* Returns true if expired or missing 'exp'.
|
|
35
|
+
*/
|
|
33
36
|
function isTokenExpired(payload) {
|
|
34
37
|
if (!payload.exp)
|
|
35
38
|
return true;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
3
|
-
import type { VerificationResult } from './types';
|
|
1
|
+
import { type JwtPayload, type Secret, VerifyOptions } from 'jsonwebtoken';
|
|
2
|
+
import { VerificationResult } from './types';
|
|
4
3
|
/**
|
|
5
|
-
* Verify token (throws if invalid or expired)
|
|
4
|
+
* Verify token (throws UnauthorizedError if invalid or expired)
|
|
6
5
|
*/
|
|
7
6
|
export declare const verifyToken: (token: string, secret: Secret) => string | JwtPayload;
|
|
8
7
|
/**
|
|
9
|
-
*
|
|
8
|
+
* Verify token with options
|
|
10
9
|
*/
|
|
11
|
-
export declare const
|
|
10
|
+
export declare const verifyTokenWithOptions: (token: string, secret: Secret, options?: VerifyOptions) => string | JwtPayload;
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
12
|
+
* Safe verify — never throws, returns structured result with UnauthorizedError on failure
|
|
14
13
|
*/
|
|
15
|
-
export declare const
|
|
14
|
+
export declare const safeVerifyToken: (token: string, secret: Secret) => VerificationResult;
|
|
16
15
|
/**
|
|
17
|
-
* Safe verify with
|
|
16
|
+
* Safe verify with options — never throws, returns structured result with UnauthorizedError on failure
|
|
18
17
|
*/
|
|
19
|
-
export declare const safeVerifyTokenWithOptions: (token: string, secret: Secret, options?:
|
|
18
|
+
export declare const safeVerifyTokenWithOptions: (token: string, secret: Secret, options?: VerifyOptions) => VerificationResult;
|
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.safeVerifyTokenWithOptions = exports.
|
|
3
|
+
exports.safeVerifyTokenWithOptions = exports.safeVerifyToken = exports.verifyTokenWithOptions = exports.verifyToken = void 0;
|
|
4
4
|
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
5
|
+
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
5
6
|
/**
|
|
6
|
-
* Verify token (throws if invalid or expired)
|
|
7
|
+
* Verify token (throws UnauthorizedError if invalid or expired)
|
|
7
8
|
*/
|
|
8
9
|
const verifyToken = (token, secret) => {
|
|
9
|
-
|
|
10
|
+
try {
|
|
11
|
+
return (0, jsonwebtoken_1.verify)(token, secret);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.name === 'TokenExpiredError') {
|
|
15
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Token has expired' }, error);
|
|
16
|
+
}
|
|
17
|
+
if (error.name === 'JsonWebTokenError') {
|
|
18
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Invalid token' }, error);
|
|
19
|
+
}
|
|
20
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
21
|
+
}
|
|
10
22
|
};
|
|
11
23
|
exports.verifyToken = verifyToken;
|
|
12
24
|
/**
|
|
13
|
-
*
|
|
25
|
+
* Verify token with options
|
|
26
|
+
*/
|
|
27
|
+
const verifyTokenWithOptions = (token, secret, options = {}) => {
|
|
28
|
+
try {
|
|
29
|
+
return (0, jsonwebtoken_1.verify)(token, secret, options);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.name === 'TokenExpiredError') {
|
|
33
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Token has expired' }, error);
|
|
34
|
+
}
|
|
35
|
+
if (error.name === 'JsonWebTokenError') {
|
|
36
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Invalid token' }, error);
|
|
37
|
+
}
|
|
38
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
exports.verifyTokenWithOptions = verifyTokenWithOptions;
|
|
42
|
+
/**
|
|
43
|
+
* Safe verify — never throws, returns structured result with UnauthorizedError on failure
|
|
14
44
|
*/
|
|
15
45
|
const safeVerifyToken = (token, secret) => {
|
|
16
46
|
try {
|
|
@@ -18,19 +48,22 @@ const safeVerifyToken = (token, secret) => {
|
|
|
18
48
|
return { valid: true, payload: decoded };
|
|
19
49
|
}
|
|
20
50
|
catch (error) {
|
|
21
|
-
|
|
51
|
+
let wrappedError;
|
|
52
|
+
if (error.name === 'TokenExpiredError') {
|
|
53
|
+
wrappedError = new errors_utils_1.UnauthorizedError({ message: 'Token has expired' }, error);
|
|
54
|
+
}
|
|
55
|
+
else if (error.name === 'JsonWebTokenError') {
|
|
56
|
+
wrappedError = new errors_utils_1.UnauthorizedError({ message: 'Invalid token' }, error);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
wrappedError = new errors_utils_1.UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
60
|
+
}
|
|
61
|
+
return { valid: false, error: wrappedError };
|
|
22
62
|
}
|
|
23
63
|
};
|
|
24
64
|
exports.safeVerifyToken = safeVerifyToken;
|
|
25
65
|
/**
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
const verifyTokenWithOptions = (token, secret, options = {}) => {
|
|
29
|
-
return (0, jsonwebtoken_1.verify)(token, secret, options);
|
|
30
|
-
};
|
|
31
|
-
exports.verifyTokenWithOptions = verifyTokenWithOptions;
|
|
32
|
-
/**
|
|
33
|
-
* Safe verify with validation options
|
|
66
|
+
* Safe verify with options — never throws, returns structured result with UnauthorizedError on failure
|
|
34
67
|
*/
|
|
35
68
|
const safeVerifyTokenWithOptions = (token, secret, options = {}) => {
|
|
36
69
|
try {
|
|
@@ -38,7 +71,17 @@ const safeVerifyTokenWithOptions = (token, secret, options = {}) => {
|
|
|
38
71
|
return { valid: true, payload: decoded };
|
|
39
72
|
}
|
|
40
73
|
catch (error) {
|
|
41
|
-
|
|
74
|
+
let wrappedError;
|
|
75
|
+
if (error.name === 'TokenExpiredError') {
|
|
76
|
+
wrappedError = new errors_utils_1.UnauthorizedError({ message: 'Token has expired' }, error);
|
|
77
|
+
}
|
|
78
|
+
else if (error.name === 'JsonWebTokenError') {
|
|
79
|
+
wrappedError = new errors_utils_1.UnauthorizedError({ message: 'Invalid token' }, error);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
wrappedError = new errors_utils_1.UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
83
|
+
}
|
|
84
|
+
return { valid: false, error: wrappedError };
|
|
42
85
|
}
|
|
43
86
|
};
|
|
44
87
|
exports.safeVerifyTokenWithOptions = safeVerifyTokenWithOptions;
|
|
@@ -18,8 +18,8 @@ const hashPassword = async (password, saltRounds = 10) => {
|
|
|
18
18
|
const salt = await bcryptjs_1.default.genSalt(saltRounds);
|
|
19
19
|
return bcryptjs_1.default.hash(password, salt);
|
|
20
20
|
}
|
|
21
|
-
catch (
|
|
22
|
-
throw new errors_utils_1.InternalServerError('Password hashing failed');
|
|
21
|
+
catch (_err) {
|
|
22
|
+
throw new errors_utils_1.InternalServerError({ message: 'Password hashing failed' });
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
exports.hashPassword = hashPassword;
|
|
@@ -35,8 +35,8 @@ const hashPasswordSync = (password, saltRounds = 10) => {
|
|
|
35
35
|
const salt = bcryptjs_1.default.genSaltSync(saltRounds);
|
|
36
36
|
return bcryptjs_1.default.hashSync(password, salt);
|
|
37
37
|
}
|
|
38
|
-
catch (
|
|
39
|
-
throw new errors_utils_1.InternalServerError('Password hashing failed');
|
|
38
|
+
catch (_error) {
|
|
39
|
+
throw new errors_utils_1.InternalServerError({ message: 'Password hashing failed' });
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
exports.hashPasswordSync = hashPasswordSync;
|
|
@@ -23,7 +23,7 @@ export declare class PasswordManager implements IPasswordManager {
|
|
|
23
23
|
*/
|
|
24
24
|
checkStrength(password: string): PasswordStrength;
|
|
25
25
|
/**
|
|
26
|
-
* Check if password hash needs upgrade (
|
|
26
|
+
* Check if password hash needs upgrade (saltRounds change)
|
|
27
27
|
*/
|
|
28
|
-
needsUpgrade(
|
|
28
|
+
needsUpgrade(_hash: string, _currentConfig: PasswordConfig): boolean;
|
|
29
29
|
}
|