@naman_deep_singh/security 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +5 -4
|
@@ -1,52 +1,17 @@
|
|
|
1
|
-
import jwt from
|
|
2
|
-
import { signToken } from
|
|
3
|
-
import {
|
|
4
|
-
import { BadRequestError, UnauthorizedError, ValidationError } from
|
|
5
|
-
|
|
6
|
-
class TokenCache {
|
|
7
|
-
constructor(maxSize = 100, ttl = 5 * 60 * 1000) {
|
|
8
|
-
this.cache = new Map();
|
|
9
|
-
this.maxSize = maxSize;
|
|
10
|
-
this.ttl = ttl;
|
|
11
|
-
}
|
|
12
|
-
get(key) {
|
|
13
|
-
const entry = this.cache.get(key);
|
|
14
|
-
if (!entry)
|
|
15
|
-
return null;
|
|
16
|
-
if (Date.now() - entry.timestamp > this.ttl) {
|
|
17
|
-
this.cache.delete(key);
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
// Move to end (most recently used)
|
|
21
|
-
this.cache.delete(key);
|
|
22
|
-
this.cache.set(key, entry);
|
|
23
|
-
return { valid: entry.valid, payload: entry.payload };
|
|
24
|
-
}
|
|
25
|
-
set(key, value) {
|
|
26
|
-
if (this.cache.has(key)) {
|
|
27
|
-
this.cache.delete(key);
|
|
28
|
-
}
|
|
29
|
-
else if (this.cache.size >= this.maxSize) {
|
|
30
|
-
// Remove least recently used (first item)
|
|
31
|
-
const firstKey = this.cache.keys().next().value;
|
|
32
|
-
if (firstKey !== undefined) {
|
|
33
|
-
this.cache.delete(firstKey);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
this.cache.set(key, { ...value, timestamp: Date.now() });
|
|
37
|
-
}
|
|
38
|
-
clear() {
|
|
39
|
-
this.cache.clear();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import { signToken } from './signToken';
|
|
3
|
+
import { safeVerifyToken, verifyToken } from './verify';
|
|
4
|
+
import { BadRequestError, UnauthorizedError, ValidationError, } from '@naman_deep_singh/errors-utils';
|
|
5
|
+
import { LRUCache } from '@naman_deep_singh/js-extensions';
|
|
42
6
|
export class JWTManager {
|
|
43
7
|
constructor(config) {
|
|
44
8
|
this.accessSecret = config.accessSecret;
|
|
45
9
|
this.refreshSecret = config.refreshSecret;
|
|
46
|
-
this.accessExpiry = config.accessExpiry ||
|
|
47
|
-
this.refreshExpiry = config.refreshExpiry ||
|
|
10
|
+
this.accessExpiry = config.accessExpiry || '15m';
|
|
11
|
+
this.refreshExpiry = config.refreshExpiry || '7d';
|
|
12
|
+
this.cacheTTL = 5 * 60 * 1000; // 5 minutes default TTL
|
|
48
13
|
if (config.enableCaching) {
|
|
49
|
-
this.cache = new
|
|
14
|
+
this.cache = new LRUCache(config.maxCacheSize || 100);
|
|
50
15
|
}
|
|
51
16
|
}
|
|
52
17
|
/**
|
|
@@ -63,10 +28,11 @@ export class JWTManager {
|
|
|
63
28
|
};
|
|
64
29
|
}
|
|
65
30
|
catch (error) {
|
|
66
|
-
if (error instanceof BadRequestError ||
|
|
31
|
+
if (error instanceof BadRequestError ||
|
|
32
|
+
error instanceof ValidationError) {
|
|
67
33
|
throw error;
|
|
68
34
|
}
|
|
69
|
-
throw new BadRequestError(
|
|
35
|
+
throw new BadRequestError('Failed to generate tokens');
|
|
70
36
|
}
|
|
71
37
|
}
|
|
72
38
|
/**
|
|
@@ -75,14 +41,17 @@ export class JWTManager {
|
|
|
75
41
|
async generateAccessToken(payload) {
|
|
76
42
|
try {
|
|
77
43
|
this.validatePayload(payload);
|
|
78
|
-
const token = signToken(payload, this.accessSecret, this.accessExpiry, {
|
|
44
|
+
const token = signToken(payload, this.accessSecret, this.accessExpiry, {
|
|
45
|
+
algorithm: 'HS256',
|
|
46
|
+
});
|
|
79
47
|
return token;
|
|
80
48
|
}
|
|
81
49
|
catch (error) {
|
|
82
|
-
if (error instanceof BadRequestError ||
|
|
50
|
+
if (error instanceof BadRequestError ||
|
|
51
|
+
error instanceof ValidationError) {
|
|
83
52
|
throw error;
|
|
84
53
|
}
|
|
85
|
-
throw new BadRequestError(
|
|
54
|
+
throw new BadRequestError('Failed to generate access token');
|
|
86
55
|
}
|
|
87
56
|
}
|
|
88
57
|
/**
|
|
@@ -91,14 +60,17 @@ export class JWTManager {
|
|
|
91
60
|
async generateRefreshToken(payload) {
|
|
92
61
|
try {
|
|
93
62
|
this.validatePayload(payload);
|
|
94
|
-
const token = signToken(payload, this.refreshSecret, this.refreshExpiry, {
|
|
63
|
+
const token = signToken(payload, this.refreshSecret, this.refreshExpiry, {
|
|
64
|
+
algorithm: 'HS256',
|
|
65
|
+
});
|
|
95
66
|
return token;
|
|
96
67
|
}
|
|
97
68
|
catch (error) {
|
|
98
|
-
if (error instanceof BadRequestError ||
|
|
69
|
+
if (error instanceof BadRequestError ||
|
|
70
|
+
error instanceof ValidationError) {
|
|
99
71
|
throw error;
|
|
100
72
|
}
|
|
101
|
-
throw new BadRequestError(
|
|
73
|
+
throw new BadRequestError('Failed to generate refresh token');
|
|
102
74
|
}
|
|
103
75
|
}
|
|
104
76
|
/**
|
|
@@ -106,36 +78,41 @@ export class JWTManager {
|
|
|
106
78
|
*/
|
|
107
79
|
async verifyAccessToken(token) {
|
|
108
80
|
try {
|
|
109
|
-
if (!token || typeof token !==
|
|
110
|
-
throw new ValidationError(
|
|
81
|
+
if (!token || typeof token !== 'string') {
|
|
82
|
+
throw new ValidationError('Access token must be a non-empty string');
|
|
111
83
|
}
|
|
112
84
|
const cacheKey = `access_${token}`;
|
|
113
85
|
if (this.cache) {
|
|
114
86
|
const cached = this.cache.get(cacheKey);
|
|
115
|
-
if (cached) {
|
|
87
|
+
if (cached && Date.now() - cached.timestamp <= this.cacheTTL) {
|
|
116
88
|
if (!cached.valid) {
|
|
117
|
-
throw new UnauthorizedError(
|
|
89
|
+
throw new UnauthorizedError('Access token is invalid or expired');
|
|
118
90
|
}
|
|
119
91
|
return cached.payload;
|
|
120
92
|
}
|
|
121
93
|
}
|
|
122
94
|
const decoded = verifyToken(token, this.accessSecret);
|
|
123
95
|
if (this.cache) {
|
|
124
|
-
this.cache.set(cacheKey, {
|
|
96
|
+
this.cache.set(cacheKey, {
|
|
97
|
+
valid: true,
|
|
98
|
+
payload: decoded,
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
});
|
|
125
101
|
}
|
|
126
102
|
return decoded;
|
|
127
103
|
}
|
|
128
104
|
catch (error) {
|
|
129
|
-
if (error instanceof ValidationError ||
|
|
105
|
+
if (error instanceof ValidationError ||
|
|
106
|
+
error instanceof UnauthorizedError) {
|
|
130
107
|
throw error;
|
|
131
108
|
}
|
|
132
|
-
if (error instanceof Error && error.name ===
|
|
133
|
-
throw new UnauthorizedError(
|
|
109
|
+
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
110
|
+
throw new UnauthorizedError('Access token has expired');
|
|
134
111
|
}
|
|
135
|
-
if (error instanceof Error && error.name ===
|
|
136
|
-
throw new UnauthorizedError(
|
|
112
|
+
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
113
|
+
throw new UnauthorizedError('Access token is invalid');
|
|
137
114
|
}
|
|
138
|
-
throw new UnauthorizedError(
|
|
115
|
+
throw new UnauthorizedError('Failed to verify access token');
|
|
139
116
|
}
|
|
140
117
|
}
|
|
141
118
|
/**
|
|
@@ -143,36 +120,37 @@ export class JWTManager {
|
|
|
143
120
|
*/
|
|
144
121
|
async verifyRefreshToken(token) {
|
|
145
122
|
try {
|
|
146
|
-
if (!token || typeof token !==
|
|
147
|
-
throw new ValidationError(
|
|
123
|
+
if (!token || typeof token !== 'string') {
|
|
124
|
+
throw new ValidationError('Refresh token must be a non-empty string');
|
|
148
125
|
}
|
|
149
126
|
const cacheKey = `refresh_${token}`;
|
|
150
127
|
if (this.cache) {
|
|
151
128
|
const cached = this.cache.get(cacheKey);
|
|
152
129
|
if (cached) {
|
|
153
130
|
if (!cached.valid) {
|
|
154
|
-
throw new UnauthorizedError(
|
|
131
|
+
throw new UnauthorizedError('Refresh token is invalid or expired');
|
|
155
132
|
}
|
|
156
133
|
return cached.payload;
|
|
157
134
|
}
|
|
158
135
|
}
|
|
159
136
|
const decoded = verifyToken(token, this.refreshSecret);
|
|
160
137
|
if (this.cache) {
|
|
161
|
-
this.cache.set(cacheKey, { valid: true, payload: decoded });
|
|
138
|
+
this.cache.set(cacheKey, { valid: true, payload: decoded, timestamp: Date.now() });
|
|
162
139
|
}
|
|
163
140
|
return decoded;
|
|
164
141
|
}
|
|
165
142
|
catch (error) {
|
|
166
|
-
if (error instanceof ValidationError ||
|
|
143
|
+
if (error instanceof ValidationError ||
|
|
144
|
+
error instanceof UnauthorizedError) {
|
|
167
145
|
throw error;
|
|
168
146
|
}
|
|
169
|
-
if (error instanceof Error && error.name ===
|
|
170
|
-
throw new UnauthorizedError(
|
|
147
|
+
if (error instanceof Error && error.name === 'TokenExpiredError') {
|
|
148
|
+
throw new UnauthorizedError('Refresh token has expired');
|
|
171
149
|
}
|
|
172
|
-
if (error instanceof Error && error.name ===
|
|
173
|
-
throw new UnauthorizedError(
|
|
150
|
+
if (error instanceof Error && error.name === 'JsonWebTokenError') {
|
|
151
|
+
throw new UnauthorizedError('Refresh token is invalid');
|
|
174
152
|
}
|
|
175
|
-
throw new UnauthorizedError(
|
|
153
|
+
throw new UnauthorizedError('Failed to verify refresh token');
|
|
176
154
|
}
|
|
177
155
|
}
|
|
178
156
|
/**
|
|
@@ -180,8 +158,8 @@ export class JWTManager {
|
|
|
180
158
|
*/
|
|
181
159
|
decodeToken(token, complete = false) {
|
|
182
160
|
try {
|
|
183
|
-
if (!token || typeof token !==
|
|
184
|
-
throw new ValidationError(
|
|
161
|
+
if (!token || typeof token !== 'string') {
|
|
162
|
+
throw new ValidationError('Token must be a non-empty string');
|
|
185
163
|
}
|
|
186
164
|
return jwt.decode(token, { complete });
|
|
187
165
|
}
|
|
@@ -197,11 +175,11 @@ export class JWTManager {
|
|
|
197
175
|
*/
|
|
198
176
|
extractTokenFromHeader(authHeader) {
|
|
199
177
|
try {
|
|
200
|
-
if (!authHeader || typeof authHeader !==
|
|
178
|
+
if (!authHeader || typeof authHeader !== 'string') {
|
|
201
179
|
return null;
|
|
202
180
|
}
|
|
203
|
-
const parts = authHeader.split(
|
|
204
|
-
if (parts.length !== 2 || parts[0] !==
|
|
181
|
+
const parts = authHeader.split(' ');
|
|
182
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
205
183
|
return null;
|
|
206
184
|
}
|
|
207
185
|
return parts[1];
|
|
@@ -215,7 +193,7 @@ export class JWTManager {
|
|
|
215
193
|
*/
|
|
216
194
|
validateToken(token, secret, options = {}) {
|
|
217
195
|
try {
|
|
218
|
-
if (!token || typeof token !==
|
|
196
|
+
if (!token || typeof token !== 'string') {
|
|
219
197
|
return false;
|
|
220
198
|
}
|
|
221
199
|
const result = safeVerifyToken(token, secret);
|
|
@@ -230,12 +208,12 @@ export class JWTManager {
|
|
|
230
208
|
*/
|
|
231
209
|
async rotateRefreshToken(oldToken) {
|
|
232
210
|
try {
|
|
233
|
-
if (!oldToken || typeof oldToken !==
|
|
234
|
-
throw new ValidationError(
|
|
211
|
+
if (!oldToken || typeof oldToken !== 'string') {
|
|
212
|
+
throw new ValidationError('Old refresh token must be a non-empty string');
|
|
235
213
|
}
|
|
236
214
|
const decoded = await this.verifyRefreshToken(oldToken);
|
|
237
|
-
if (typeof decoded ===
|
|
238
|
-
throw new ValidationError(
|
|
215
|
+
if (typeof decoded === 'string') {
|
|
216
|
+
throw new ValidationError('Invalid token payload — expected JWT payload object');
|
|
239
217
|
}
|
|
240
218
|
// Create new payload without issued/expired timestamps
|
|
241
219
|
const payload = { ...decoded };
|
|
@@ -246,10 +224,11 @@ export class JWTManager {
|
|
|
246
224
|
return newToken;
|
|
247
225
|
}
|
|
248
226
|
catch (error) {
|
|
249
|
-
if (error instanceof ValidationError ||
|
|
227
|
+
if (error instanceof ValidationError ||
|
|
228
|
+
error instanceof UnauthorizedError) {
|
|
250
229
|
throw error;
|
|
251
230
|
}
|
|
252
|
-
throw new BadRequestError(
|
|
231
|
+
throw new BadRequestError('Failed to rotate refresh token');
|
|
253
232
|
}
|
|
254
233
|
}
|
|
255
234
|
/**
|
|
@@ -295,18 +274,19 @@ export class JWTManager {
|
|
|
295
274
|
getCacheStats() {
|
|
296
275
|
if (!this.cache)
|
|
297
276
|
return null;
|
|
277
|
+
// Note: LRUCache doesn't expose internal size, so we return maxSize only
|
|
298
278
|
return {
|
|
299
|
-
size:
|
|
300
|
-
maxSize: this.cache.maxSize
|
|
279
|
+
size: -1, // Size not available from LRUCache
|
|
280
|
+
maxSize: this.cache.maxSize,
|
|
301
281
|
};
|
|
302
282
|
}
|
|
303
283
|
// Private helper methods
|
|
304
284
|
validatePayload(payload) {
|
|
305
|
-
if (!payload || typeof payload !==
|
|
306
|
-
throw new ValidationError(
|
|
285
|
+
if (!payload || typeof payload !== 'object') {
|
|
286
|
+
throw new ValidationError('Payload must be a non-null object');
|
|
307
287
|
}
|
|
308
288
|
if (Object.keys(payload).length === 0) {
|
|
309
|
-
throw new ValidationError(
|
|
289
|
+
throw new ValidationError('Payload cannot be empty');
|
|
310
290
|
}
|
|
311
291
|
}
|
|
312
292
|
}
|
|
@@ -3,16 +3,16 @@ const TIME_UNITS = {
|
|
|
3
3
|
m: 60,
|
|
4
4
|
h: 3600,
|
|
5
5
|
d: 86400,
|
|
6
|
-
w: 604800
|
|
6
|
+
w: 604800,
|
|
7
7
|
};
|
|
8
8
|
export function parseDuration(input) {
|
|
9
|
-
if (typeof input ===
|
|
9
|
+
if (typeof input === 'number')
|
|
10
10
|
return input;
|
|
11
11
|
const regex = /(\d+)\s*(s|m|h|d|w)/gi;
|
|
12
12
|
let totalSeconds = 0;
|
|
13
13
|
let match;
|
|
14
14
|
while ((match = regex.exec(input)) !== null) {
|
|
15
|
-
const value = parseInt(match[1], 10);
|
|
15
|
+
const value = Number.parseInt(match[1], 10);
|
|
16
16
|
const unit = match[2].toLowerCase();
|
|
17
17
|
if (!TIME_UNITS[unit]) {
|
|
18
18
|
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;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { sign } from
|
|
2
|
-
import { parseDuration } from
|
|
1
|
+
import { sign } from 'jsonwebtoken';
|
|
2
|
+
import { parseDuration } from './parseDuration';
|
|
3
3
|
function getExpiryTimestamp(seconds) {
|
|
4
4
|
return Math.floor(Date.now() / 1000) + seconds;
|
|
5
5
|
}
|
|
6
|
-
export const signToken = (payload, secret, expiresIn =
|
|
6
|
+
export const signToken = (payload, secret, expiresIn = '1h', options = {}) => {
|
|
7
7
|
const seconds = parseDuration(expiresIn);
|
|
8
8
|
if (!seconds || seconds < 10) {
|
|
9
|
-
throw new Error(
|
|
9
|
+
throw new Error('Token expiry too small');
|
|
10
10
|
}
|
|
11
11
|
const tokenPayload = {
|
|
12
|
-
...payload
|
|
12
|
+
...payload,
|
|
13
13
|
};
|
|
14
|
-
if (!(
|
|
14
|
+
if (!('exp' in payload))
|
|
15
15
|
tokenPayload.exp = getExpiryTimestamp(seconds);
|
|
16
|
-
if (!(
|
|
16
|
+
if (!('iat' in payload))
|
|
17
17
|
tokenPayload.iat = Math.floor(Date.now() / 1000);
|
|
18
18
|
return sign(tokenPayload, secret, {
|
|
19
|
-
algorithm:
|
|
20
|
-
...options
|
|
19
|
+
algorithm: 'HS256',
|
|
20
|
+
...options,
|
|
21
21
|
});
|
|
22
22
|
};
|
|
@@ -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;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function validateTokenPayload(payload, rules = {
|
|
2
|
-
requiredFields: [
|
|
2
|
+
requiredFields: ['exp', 'iat'],
|
|
3
3
|
}) {
|
|
4
|
-
const { requiredFields = [], forbiddenFields = [], validateTypes = {} } = rules;
|
|
4
|
+
const { requiredFields = [], forbiddenFields = [], validateTypes = {}, } = rules;
|
|
5
5
|
// 1. Required fields
|
|
6
6
|
for (const field of requiredFields) {
|
|
7
7
|
if (!(field in payload)) {
|
|
@@ -20,7 +20,7 @@ export function validateTokenPayload(payload, rules = {
|
|
|
20
20
|
if (key in payload && typeof payload[key] !== expectedType) {
|
|
21
21
|
return {
|
|
22
22
|
valid: false,
|
|
23
|
-
error: `Invalid type for ${key}. Expected ${expectedType}
|
|
23
|
+
error: `Invalid type for ${key}. Expected ${expectedType}.`,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import jwt
|
|
2
|
-
import {
|
|
1
|
+
import type jwt from 'jsonwebtoken';
|
|
2
|
+
import { type JwtPayload, type Secret } from 'jsonwebtoken';
|
|
3
|
+
import type { VerificationResult } from './types';
|
|
3
4
|
/**
|
|
4
5
|
* Verify token (throws if invalid or expired)
|
|
5
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { InternalServerError } from '@naman_deep_singh/errors-utils';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import { ensureValidPassword } from './utils';
|
|
4
4
|
/**
|
|
5
5
|
* Hash a password asynchronously using bcrypt.
|
|
6
6
|
*/
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './hash';
|
|
2
|
+
export * from './strength';
|
|
3
|
+
export * from './verify';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './hash';
|
|
2
|
+
export * from './strength';
|
|
3
|
+
export * from './verify';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IPasswordManager, PasswordConfig,
|
|
1
|
+
import type { HashedPassword, IPasswordManager, PasswordConfig, PasswordStrength, PasswordValidationResult } from '../../interfaces/password.interface';
|
|
2
2
|
export declare class PasswordManager implements IPasswordManager {
|
|
3
3
|
private defaultConfig;
|
|
4
4
|
constructor(config?: PasswordConfig);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import { BadRequestError, ValidationError, } from '@naman_deep_singh/errors-utils';
|
|
4
|
+
import { ensureValidPassword, estimatePasswordEntropy } from './utils';
|
|
5
5
|
export class PasswordManager {
|
|
6
6
|
constructor(config = {}) {
|
|
7
7
|
this.defaultConfig = {
|
|
@@ -12,7 +12,7 @@ export class PasswordManager {
|
|
|
12
12
|
requireLowercase: true,
|
|
13
13
|
requireNumbers: true,
|
|
14
14
|
requireSpecialChars: false,
|
|
15
|
-
...config
|
|
15
|
+
...config,
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -31,14 +31,15 @@ export class PasswordManager {
|
|
|
31
31
|
const hash = await bcrypt.hash(password, passwordSalt);
|
|
32
32
|
return {
|
|
33
33
|
hash,
|
|
34
|
-
salt: passwordSalt
|
|
34
|
+
salt: passwordSalt,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
if (error instanceof BadRequestError ||
|
|
38
|
+
if (error instanceof BadRequestError ||
|
|
39
|
+
error instanceof ValidationError) {
|
|
39
40
|
throw error;
|
|
40
41
|
}
|
|
41
|
-
throw new BadRequestError(
|
|
42
|
+
throw new BadRequestError('Failed to hash password');
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
@@ -70,14 +71,14 @@ export class PasswordManager {
|
|
|
70
71
|
if (length < config.minLength || length > config.maxLength) {
|
|
71
72
|
throw new ValidationError(`Password length must be between ${config.minLength} and ${config.maxLength}`);
|
|
72
73
|
}
|
|
73
|
-
let charset =
|
|
74
|
+
let charset = 'abcdefghijklmnopqrstuvwxyz';
|
|
74
75
|
if (config.requireUppercase)
|
|
75
|
-
charset +=
|
|
76
|
+
charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
76
77
|
if (config.requireNumbers)
|
|
77
|
-
charset +=
|
|
78
|
+
charset += '0123456789';
|
|
78
79
|
if (config.requireSpecialChars)
|
|
79
|
-
charset +=
|
|
80
|
-
let password =
|
|
80
|
+
charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
81
|
+
let password = '';
|
|
81
82
|
const randomBytes = crypto.randomBytes(length);
|
|
82
83
|
for (let i = 0; i < length; i++) {
|
|
83
84
|
password += charset[randomBytes[i] % charset.length];
|
|
@@ -104,8 +105,8 @@ export class PasswordManager {
|
|
|
104
105
|
const finalConfig = { ...this.defaultConfig, ...config };
|
|
105
106
|
const errors = [];
|
|
106
107
|
// Basic validation
|
|
107
|
-
if (!password || typeof password !==
|
|
108
|
-
errors.push(
|
|
108
|
+
if (!password || typeof password !== 'string') {
|
|
109
|
+
errors.push('Password must be a non-empty string');
|
|
109
110
|
}
|
|
110
111
|
// Length validation
|
|
111
112
|
if (password.length < finalConfig.minLength) {
|
|
@@ -116,20 +117,20 @@ export class PasswordManager {
|
|
|
116
117
|
}
|
|
117
118
|
// Complexity requirements
|
|
118
119
|
if (finalConfig.requireUppercase && !/[A-Z]/.test(password)) {
|
|
119
|
-
errors.push(
|
|
120
|
+
errors.push('Password must contain at least one uppercase letter');
|
|
120
121
|
}
|
|
121
122
|
if (finalConfig.requireLowercase && !/[a-z]/.test(password)) {
|
|
122
|
-
errors.push(
|
|
123
|
+
errors.push('Password must contain at least one lowercase letter');
|
|
123
124
|
}
|
|
124
125
|
if (finalConfig.requireNumbers && !/[0-9]/.test(password)) {
|
|
125
|
-
errors.push(
|
|
126
|
+
errors.push('Password must contain at least one number');
|
|
126
127
|
}
|
|
127
128
|
if (finalConfig.requireSpecialChars && !/[^A-Za-z0-9]/.test(password)) {
|
|
128
|
-
errors.push(
|
|
129
|
+
errors.push('Password must contain at least one special character');
|
|
129
130
|
}
|
|
130
131
|
// Custom rules
|
|
131
132
|
if (finalConfig.customRules) {
|
|
132
|
-
finalConfig.customRules.forEach(rule => {
|
|
133
|
+
finalConfig.customRules.forEach((rule) => {
|
|
133
134
|
if (!rule.test(password)) {
|
|
134
135
|
errors.push(rule.message);
|
|
135
136
|
}
|
|
@@ -140,7 +141,7 @@ export class PasswordManager {
|
|
|
140
141
|
return {
|
|
141
142
|
isValid,
|
|
142
143
|
errors,
|
|
143
|
-
strength
|
|
144
|
+
strength,
|
|
144
145
|
};
|
|
145
146
|
}
|
|
146
147
|
/**
|
|
@@ -170,25 +171,25 @@ export class PasswordManager {
|
|
|
170
171
|
// Common patterns deduction
|
|
171
172
|
if (/^[A-Za-z]+$/.test(password)) {
|
|
172
173
|
score--;
|
|
173
|
-
feedback.push(
|
|
174
|
+
feedback.push('Consider adding numbers and symbols');
|
|
174
175
|
}
|
|
175
176
|
if (/^[0-9]+$/.test(password)) {
|
|
176
177
|
score -= 2;
|
|
177
|
-
feedback.push(
|
|
178
|
+
feedback.push('Avoid using only numbers');
|
|
178
179
|
}
|
|
179
180
|
if (/([a-zA-Z0-9])\1{2,}/.test(password)) {
|
|
180
181
|
score--;
|
|
181
|
-
feedback.push(
|
|
182
|
+
feedback.push('Avoid repeated characters');
|
|
182
183
|
}
|
|
183
184
|
if (/(?:012|123|234|345|456|567|678|789)/.test(password)) {
|
|
184
185
|
score--;
|
|
185
|
-
feedback.push(
|
|
186
|
+
feedback.push('Avoid sequential patterns');
|
|
186
187
|
}
|
|
187
188
|
// Common passwords check
|
|
188
189
|
const commonPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
|
|
189
|
-
if (commonPasswords.some(common => password.toLowerCase().includes(common))) {
|
|
190
|
+
if (commonPasswords.some((common) => password.toLowerCase().includes(common))) {
|
|
190
191
|
score = 0;
|
|
191
|
-
feedback.push(
|
|
192
|
+
feedback.push('Avoid common passwords');
|
|
192
193
|
}
|
|
193
194
|
// Clamp score and determine label
|
|
194
195
|
score = Math.max(0, Math.min(4, score));
|
|
@@ -196,23 +197,23 @@ export class PasswordManager {
|
|
|
196
197
|
switch (score) {
|
|
197
198
|
case 0:
|
|
198
199
|
label = 'very-weak';
|
|
199
|
-
suggestions.push(
|
|
200
|
+
suggestions.push('Use a longer password with mixed characters');
|
|
200
201
|
break;
|
|
201
202
|
case 1:
|
|
202
203
|
label = 'weak';
|
|
203
|
-
suggestions.push(
|
|
204
|
+
suggestions.push('Add more character variety');
|
|
204
205
|
break;
|
|
205
206
|
case 2:
|
|
206
207
|
label = 'fair';
|
|
207
|
-
suggestions.push(
|
|
208
|
+
suggestions.push('Consider adding more length or character types');
|
|
208
209
|
break;
|
|
209
210
|
case 3:
|
|
210
211
|
label = 'good';
|
|
211
|
-
suggestions.push(
|
|
212
|
+
suggestions.push('Your password is reasonably secure');
|
|
212
213
|
break;
|
|
213
214
|
case 4:
|
|
214
215
|
label = 'strong';
|
|
215
|
-
suggestions.push(
|
|
216
|
+
suggestions.push('Your password is very secure');
|
|
216
217
|
break;
|
|
217
218
|
default:
|
|
218
219
|
label = 'very-weak';
|
|
@@ -221,7 +222,7 @@ export class PasswordManager {
|
|
|
221
222
|
score,
|
|
222
223
|
label,
|
|
223
224
|
feedback,
|
|
224
|
-
suggestions
|
|
225
|
+
suggestions,
|
|
225
226
|
};
|
|
226
227
|
}
|
|
227
228
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { PasswordStrengthOptions } from
|
|
1
|
+
import type { PasswordStrengthOptions } from './types';
|
|
2
2
|
export declare const isPasswordStrong: (password: string, options?: PasswordStrengthOptions) => boolean;
|