@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
|
@@ -1,30 +1,63 @@
|
|
|
1
1
|
import { verify } from 'jsonwebtoken';
|
|
2
|
+
import { UnauthorizedError } from '@naman_deep_singh/errors-utils';
|
|
2
3
|
/**
|
|
3
|
-
* Verify token (throws if invalid or expired)
|
|
4
|
+
* Verify token (throws UnauthorizedError if invalid or expired)
|
|
4
5
|
*/
|
|
5
6
|
export const verifyToken = (token, secret) => {
|
|
6
|
-
|
|
7
|
+
try {
|
|
8
|
+
return verify(token, secret);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.name === 'TokenExpiredError') {
|
|
12
|
+
throw new UnauthorizedError({ message: 'Token has expired' }, error);
|
|
13
|
+
}
|
|
14
|
+
if (error.name === 'JsonWebTokenError') {
|
|
15
|
+
throw new UnauthorizedError({ message: 'Invalid token' }, error);
|
|
16
|
+
}
|
|
17
|
+
throw new UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
18
|
+
}
|
|
7
19
|
};
|
|
8
20
|
/**
|
|
9
|
-
*
|
|
21
|
+
* Verify token with options
|
|
10
22
|
*/
|
|
11
|
-
export const
|
|
23
|
+
export const verifyTokenWithOptions = (token, secret, options = {}) => {
|
|
12
24
|
try {
|
|
13
|
-
|
|
14
|
-
return { valid: true, payload: decoded };
|
|
25
|
+
return verify(token, secret, options);
|
|
15
26
|
}
|
|
16
27
|
catch (error) {
|
|
17
|
-
|
|
28
|
+
if (error.name === 'TokenExpiredError') {
|
|
29
|
+
throw new UnauthorizedError({ message: 'Token has expired' }, error);
|
|
30
|
+
}
|
|
31
|
+
if (error.name === 'JsonWebTokenError') {
|
|
32
|
+
throw new UnauthorizedError({ message: 'Invalid token' }, error);
|
|
33
|
+
}
|
|
34
|
+
throw new UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
18
35
|
}
|
|
19
36
|
};
|
|
20
37
|
/**
|
|
21
|
-
*
|
|
38
|
+
* Safe verify — never throws, returns structured result with UnauthorizedError on failure
|
|
22
39
|
*/
|
|
23
|
-
export const
|
|
24
|
-
|
|
40
|
+
export const safeVerifyToken = (token, secret) => {
|
|
41
|
+
try {
|
|
42
|
+
const decoded = verify(token, secret);
|
|
43
|
+
return { valid: true, payload: decoded };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
let wrappedError;
|
|
47
|
+
if (error.name === 'TokenExpiredError') {
|
|
48
|
+
wrappedError = new UnauthorizedError({ message: 'Token has expired' }, error);
|
|
49
|
+
}
|
|
50
|
+
else if (error.name === 'JsonWebTokenError') {
|
|
51
|
+
wrappedError = new UnauthorizedError({ message: 'Invalid token' }, error);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
wrappedError = new UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
55
|
+
}
|
|
56
|
+
return { valid: false, error: wrappedError };
|
|
57
|
+
}
|
|
25
58
|
};
|
|
26
59
|
/**
|
|
27
|
-
* Safe verify with
|
|
60
|
+
* Safe verify with options — never throws, returns structured result with UnauthorizedError on failure
|
|
28
61
|
*/
|
|
29
62
|
export const safeVerifyTokenWithOptions = (token, secret, options = {}) => {
|
|
30
63
|
try {
|
|
@@ -32,6 +65,16 @@ export const safeVerifyTokenWithOptions = (token, secret, options = {}) => {
|
|
|
32
65
|
return { valid: true, payload: decoded };
|
|
33
66
|
}
|
|
34
67
|
catch (error) {
|
|
35
|
-
|
|
68
|
+
let wrappedError;
|
|
69
|
+
if (error.name === 'TokenExpiredError') {
|
|
70
|
+
wrappedError = new UnauthorizedError({ message: 'Token has expired' }, error);
|
|
71
|
+
}
|
|
72
|
+
else if (error.name === 'JsonWebTokenError') {
|
|
73
|
+
wrappedError = new UnauthorizedError({ message: 'Invalid token' }, error);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
wrappedError = new UnauthorizedError({ message: 'Failed to verify token' }, error);
|
|
77
|
+
}
|
|
78
|
+
return { valid: false, error: wrappedError };
|
|
36
79
|
}
|
|
37
80
|
};
|
|
@@ -10,8 +10,8 @@ export const hashPassword = async (password, saltRounds = 10) => {
|
|
|
10
10
|
const salt = await bcrypt.genSalt(saltRounds);
|
|
11
11
|
return bcrypt.hash(password, salt);
|
|
12
12
|
}
|
|
13
|
-
catch (
|
|
14
|
-
throw new InternalServerError('Password hashing failed');
|
|
13
|
+
catch (_err) {
|
|
14
|
+
throw new InternalServerError({ message: 'Password hashing failed' });
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
export function hashPasswordWithPepper(password, pepper) {
|
|
@@ -26,8 +26,8 @@ export const hashPasswordSync = (password, saltRounds = 10) => {
|
|
|
26
26
|
const salt = bcrypt.genSaltSync(saltRounds);
|
|
27
27
|
return bcrypt.hashSync(password, salt);
|
|
28
28
|
}
|
|
29
|
-
catch (
|
|
30
|
-
throw new InternalServerError('Password hashing failed');
|
|
29
|
+
catch (_error) {
|
|
30
|
+
throw new InternalServerError({ message: 'Password hashing failed' });
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
export function hashPasswordWithPepperSync(password, pepper) {
|
|
@@ -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
|
}
|
|
@@ -21,25 +21,20 @@ export class PasswordManager {
|
|
|
21
21
|
async hash(password, salt) {
|
|
22
22
|
try {
|
|
23
23
|
ensureValidPassword(password);
|
|
24
|
-
// Validate password meets basic requirements
|
|
25
24
|
this.validate(password);
|
|
26
25
|
const saltRounds = this.defaultConfig.saltRounds;
|
|
27
|
-
let
|
|
28
|
-
if (!
|
|
29
|
-
|
|
26
|
+
let finalSalt = salt;
|
|
27
|
+
if (!finalSalt) {
|
|
28
|
+
finalSalt = await bcrypt.genSalt(saltRounds);
|
|
30
29
|
}
|
|
31
|
-
const hash = await bcrypt.hash(password,
|
|
32
|
-
return {
|
|
33
|
-
hash,
|
|
34
|
-
salt: passwordSalt,
|
|
35
|
-
};
|
|
30
|
+
const hash = await bcrypt.hash(password, finalSalt);
|
|
31
|
+
return { hash, salt: finalSalt };
|
|
36
32
|
}
|
|
37
33
|
catch (error) {
|
|
38
|
-
if (error instanceof BadRequestError ||
|
|
39
|
-
error instanceof ValidationError) {
|
|
34
|
+
if (error instanceof BadRequestError || error instanceof ValidationError) {
|
|
40
35
|
throw error;
|
|
41
36
|
}
|
|
42
|
-
throw new BadRequestError('Failed to hash password');
|
|
37
|
+
throw new BadRequestError({ message: 'Failed to hash password' }, error instanceof Error ? error : undefined);
|
|
43
38
|
}
|
|
44
39
|
}
|
|
45
40
|
/**
|
|
@@ -47,19 +42,12 @@ export class PasswordManager {
|
|
|
47
42
|
*/
|
|
48
43
|
async verify(password, hash, salt) {
|
|
49
44
|
try {
|
|
50
|
-
if (!password || !hash || !salt)
|
|
45
|
+
if (!password || !hash || !salt)
|
|
51
46
|
return false;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const isValid = await bcrypt.compare(password, hash);
|
|
55
|
-
// If invalid and different salt was used, try regenerating hash with new salt
|
|
56
|
-
if (!isValid && salt !== this.defaultConfig.saltRounds?.toString()) {
|
|
57
|
-
const newHash = await bcrypt.hash(password, salt);
|
|
58
|
-
return newHash === hash;
|
|
59
|
-
}
|
|
60
|
-
return isValid;
|
|
47
|
+
// bcrypt compare works directly with hash
|
|
48
|
+
return await bcrypt.compare(password, hash);
|
|
61
49
|
}
|
|
62
|
-
catch
|
|
50
|
+
catch {
|
|
63
51
|
return false;
|
|
64
52
|
}
|
|
65
53
|
}
|
|
@@ -69,7 +57,7 @@ export class PasswordManager {
|
|
|
69
57
|
generate(length = 16, options = {}) {
|
|
70
58
|
const config = { ...this.defaultConfig, ...options };
|
|
71
59
|
if (length < config.minLength || length > config.maxLength) {
|
|
72
|
-
throw new ValidationError(`Password length must be between ${config.minLength} and ${config.maxLength}`);
|
|
60
|
+
throw new ValidationError({ message: `Password length must be between ${config.minLength} and ${config.maxLength}` });
|
|
73
61
|
}
|
|
74
62
|
let charset = 'abcdefghijklmnopqrstuvwxyz';
|
|
75
63
|
if (config.requireUppercase)
|
|
@@ -78,24 +66,20 @@ export class PasswordManager {
|
|
|
78
66
|
charset += '0123456789';
|
|
79
67
|
if (config.requireSpecialChars)
|
|
80
68
|
charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
81
|
-
let password = '';
|
|
82
69
|
const randomBytes = crypto.randomBytes(length);
|
|
70
|
+
let password = '';
|
|
83
71
|
for (let i = 0; i < length; i++) {
|
|
84
72
|
password += charset[randomBytes[i] % charset.length];
|
|
85
73
|
}
|
|
86
|
-
// Ensure
|
|
87
|
-
if (config.requireUppercase && !/[A-Z]/.test(password))
|
|
74
|
+
// Ensure requirements
|
|
75
|
+
if (config.requireUppercase && !/[A-Z]/.test(password))
|
|
88
76
|
password = password.replace(/[a-z]/, 'A');
|
|
89
|
-
|
|
90
|
-
if (config.requireLowercase && !/[a-z]/.test(password)) {
|
|
77
|
+
if (config.requireLowercase && !/[a-z]/.test(password))
|
|
91
78
|
password = password.replace(/[A-Z]/, 'a');
|
|
92
|
-
|
|
93
|
-
if (config.requireNumbers && !/[0-9]/.test(password)) {
|
|
79
|
+
if (config.requireNumbers && !/[0-9]/.test(password))
|
|
94
80
|
password = password.replace(/[A-Za-z]/, '0');
|
|
95
|
-
|
|
96
|
-
if (config.requireSpecialChars && !/[^A-Za-z0-9]/.test(password)) {
|
|
81
|
+
if (config.requireSpecialChars && !/[^A-Za-z0-9]/.test(password))
|
|
97
82
|
password = password.replace(/[A-Za-z0-9]/, '!');
|
|
98
|
-
}
|
|
99
83
|
return password;
|
|
100
84
|
}
|
|
101
85
|
/**
|
|
@@ -104,45 +88,27 @@ export class PasswordManager {
|
|
|
104
88
|
validate(password, config = {}) {
|
|
105
89
|
const finalConfig = { ...this.defaultConfig, ...config };
|
|
106
90
|
const errors = [];
|
|
107
|
-
|
|
108
|
-
if (!password || typeof password !== 'string') {
|
|
91
|
+
if (!password || typeof password !== 'string')
|
|
109
92
|
errors.push('Password must be a non-empty string');
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (password.length
|
|
113
|
-
errors.push(`Password must be at least ${finalConfig.minLength} characters long`);
|
|
114
|
-
}
|
|
115
|
-
if (password.length > finalConfig.maxLength) {
|
|
93
|
+
if (password.length < finalConfig.minLength)
|
|
94
|
+
errors.push(`Password must be at least ${finalConfig.minLength} characters`);
|
|
95
|
+
if (password.length > finalConfig.maxLength)
|
|
116
96
|
errors.push(`Password must not exceed ${finalConfig.maxLength} characters`);
|
|
117
|
-
|
|
118
|
-
// Complexity requirements
|
|
119
|
-
if (finalConfig.requireUppercase && !/[A-Z]/.test(password)) {
|
|
97
|
+
if (finalConfig.requireUppercase && !/[A-Z]/.test(password))
|
|
120
98
|
errors.push('Password must contain at least one uppercase letter');
|
|
121
|
-
|
|
122
|
-
if (finalConfig.requireLowercase && !/[a-z]/.test(password)) {
|
|
99
|
+
if (finalConfig.requireLowercase && !/[a-z]/.test(password))
|
|
123
100
|
errors.push('Password must contain at least one lowercase letter');
|
|
124
|
-
|
|
125
|
-
if (finalConfig.requireNumbers && !/[0-9]/.test(password)) {
|
|
101
|
+
if (finalConfig.requireNumbers && !/[0-9]/.test(password))
|
|
126
102
|
errors.push('Password must contain at least one number');
|
|
127
|
-
|
|
128
|
-
if (finalConfig.requireSpecialChars && !/[^A-Za-z0-9]/.test(password)) {
|
|
103
|
+
if (finalConfig.requireSpecialChars && !/[^A-Za-z0-9]/.test(password))
|
|
129
104
|
errors.push('Password must contain at least one special character');
|
|
130
|
-
}
|
|
131
|
-
// Custom rules
|
|
132
105
|
if (finalConfig.customRules) {
|
|
133
106
|
finalConfig.customRules.forEach((rule) => {
|
|
134
|
-
if (!rule.test(password))
|
|
107
|
+
if (!rule.test(password))
|
|
135
108
|
errors.push(rule.message);
|
|
136
|
-
}
|
|
137
109
|
});
|
|
138
110
|
}
|
|
139
|
-
|
|
140
|
-
const isValid = errors.length === 0;
|
|
141
|
-
return {
|
|
142
|
-
isValid,
|
|
143
|
-
errors,
|
|
144
|
-
strength,
|
|
145
|
-
};
|
|
111
|
+
return { isValid: errors.length === 0, errors, strength: this.checkStrength(password) };
|
|
146
112
|
}
|
|
147
113
|
/**
|
|
148
114
|
* Check password strength
|
|
@@ -152,14 +118,20 @@ export class PasswordManager {
|
|
|
152
118
|
let score = 0;
|
|
153
119
|
const feedback = [];
|
|
154
120
|
const suggestions = [];
|
|
155
|
-
|
|
156
|
-
|
|
121
|
+
if (entropy < 28) {
|
|
122
|
+
feedback.push('Password is easy to guess');
|
|
123
|
+
suggestions.push('Use more unique characters and length');
|
|
124
|
+
}
|
|
125
|
+
else if (entropy < 36)
|
|
157
126
|
score++;
|
|
127
|
+
else if (entropy < 60)
|
|
128
|
+
score += 2;
|
|
129
|
+
else
|
|
130
|
+
score += 3;
|
|
158
131
|
if (password.length >= 12)
|
|
159
132
|
score++;
|
|
160
133
|
if (password.length >= 16)
|
|
161
134
|
score++;
|
|
162
|
-
// Character variety scoring
|
|
163
135
|
if (/[a-z]/.test(password))
|
|
164
136
|
score++;
|
|
165
137
|
if (/[A-Z]/.test(password))
|
|
@@ -168,10 +140,9 @@ export class PasswordManager {
|
|
|
168
140
|
score++;
|
|
169
141
|
if (/[^A-Za-z0-9]/.test(password))
|
|
170
142
|
score++;
|
|
171
|
-
// Common patterns deduction
|
|
172
143
|
if (/^[A-Za-z]+$/.test(password)) {
|
|
173
144
|
score--;
|
|
174
|
-
feedback.push('Consider adding numbers
|
|
145
|
+
feedback.push('Consider adding numbers or symbols');
|
|
175
146
|
}
|
|
176
147
|
if (/^[0-9]+$/.test(password)) {
|
|
177
148
|
score -= 2;
|
|
@@ -185,13 +156,11 @@ export class PasswordManager {
|
|
|
185
156
|
score--;
|
|
186
157
|
feedback.push('Avoid sequential patterns');
|
|
187
158
|
}
|
|
188
|
-
// Common passwords check
|
|
189
159
|
const commonPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
|
|
190
160
|
if (commonPasswords.some((common) => password.toLowerCase().includes(common))) {
|
|
191
161
|
score = 0;
|
|
192
162
|
feedback.push('Avoid common passwords');
|
|
193
163
|
}
|
|
194
|
-
// Clamp score and determine label
|
|
195
164
|
score = Math.max(0, Math.min(4, score));
|
|
196
165
|
let label;
|
|
197
166
|
switch (score) {
|
|
@@ -205,7 +174,7 @@ export class PasswordManager {
|
|
|
205
174
|
break;
|
|
206
175
|
case 2:
|
|
207
176
|
label = 'fair';
|
|
208
|
-
suggestions.push('Consider
|
|
177
|
+
suggestions.push('Consider increasing length or randomness');
|
|
209
178
|
break;
|
|
210
179
|
case 3:
|
|
211
180
|
label = 'good';
|
|
@@ -215,22 +184,14 @@ export class PasswordManager {
|
|
|
215
184
|
label = 'strong';
|
|
216
185
|
suggestions.push('Your password is very secure');
|
|
217
186
|
break;
|
|
218
|
-
default:
|
|
219
|
-
label = 'very-weak';
|
|
187
|
+
default: label = 'very-weak';
|
|
220
188
|
}
|
|
221
|
-
return {
|
|
222
|
-
score,
|
|
223
|
-
label,
|
|
224
|
-
feedback,
|
|
225
|
-
suggestions,
|
|
226
|
-
};
|
|
189
|
+
return { score, label, feedback, suggestions };
|
|
227
190
|
}
|
|
228
191
|
/**
|
|
229
|
-
* Check if password hash needs upgrade (
|
|
192
|
+
* Check if password hash needs upgrade (saltRounds change)
|
|
230
193
|
*/
|
|
231
|
-
needsUpgrade(
|
|
232
|
-
// Simple heuristic: if the hash doesn't match current salt rounds pattern
|
|
233
|
-
// In practice, you'd need to store the salt rounds with the hash
|
|
194
|
+
needsUpgrade(_hash, _currentConfig) {
|
|
234
195
|
return false;
|
|
235
196
|
}
|
|
236
197
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { BadRequestError, ValidationError, } from '@naman_deep_singh/errors-utils';
|
|
2
2
|
export const isPasswordStrong = (password, options = {}) => {
|
|
3
3
|
if (!password)
|
|
4
|
-
throw new BadRequestError('Invalid password provided');
|
|
4
|
+
throw new BadRequestError({ message: 'Invalid password provided' });
|
|
5
5
|
const { minLength = 8, requireUppercase = true, requireLowercase = true, requireNumbers = true, requireSymbols = false, } = options;
|
|
6
6
|
if (password.length < minLength)
|
|
7
7
|
throw new ValidationError(`Password must be at least ${minLength} characters`);
|
|
8
8
|
if (requireUppercase && !/[A-Z]/.test(password))
|
|
9
|
-
throw new ValidationError('Password must include uppercase letters');
|
|
9
|
+
throw new ValidationError({ message: 'Password must include uppercase letters' });
|
|
10
10
|
if (requireLowercase && !/[a-z]/.test(password))
|
|
11
|
-
throw new ValidationError('Password must include lowercase letters');
|
|
11
|
+
throw new ValidationError({ message: 'Password must include lowercase letters' });
|
|
12
12
|
if (requireNumbers && !/[0-9]/.test(password))
|
|
13
|
-
throw new ValidationError('Password must include numbers');
|
|
13
|
+
throw new ValidationError({ message: 'Password must include numbers' });
|
|
14
14
|
if (requireSymbols && !/[^A-Za-z0-9]/.test(password))
|
|
15
|
-
throw new ValidationError('Password must include symbols');
|
|
15
|
+
throw new ValidationError({ message: 'Password must include symbols' });
|
|
16
16
|
return true;
|
|
17
17
|
};
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure password is a valid non-empty string
|
|
3
|
+
*/
|
|
1
4
|
export declare function ensureValidPassword(password: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* Timing-safe comparison between two strings
|
|
7
|
+
*/
|
|
2
8
|
export declare function safeCompare(a: string, b: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Estimate password entropy based on character pool
|
|
11
|
+
*/
|
|
3
12
|
export declare function estimatePasswordEntropy(password: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Normalize password string to a consistent form
|
|
15
|
+
*/
|
|
4
16
|
export declare function normalizePassword(password: string): string;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { BadRequestError } from '@naman_deep_singh/errors-utils';
|
|
3
|
+
/**
|
|
4
|
+
* Ensure password is a valid non-empty string
|
|
5
|
+
*/
|
|
3
6
|
export function ensureValidPassword(password) {
|
|
4
7
|
if (!password || typeof password !== 'string') {
|
|
5
|
-
throw new BadRequestError('Invalid password provided');
|
|
8
|
+
throw new BadRequestError({ message: 'Invalid password provided' });
|
|
6
9
|
}
|
|
7
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Timing-safe comparison between two strings
|
|
13
|
+
*/
|
|
8
14
|
export function safeCompare(a, b) {
|
|
9
15
|
const bufA = Buffer.from(a);
|
|
10
16
|
const bufB = Buffer.from(b);
|
|
@@ -12,6 +18,9 @@ export function safeCompare(a, b) {
|
|
|
12
18
|
return false;
|
|
13
19
|
return crypto.timingSafeEqual(bufA, bufB);
|
|
14
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Estimate password entropy based on character pool
|
|
23
|
+
*/
|
|
15
24
|
export function estimatePasswordEntropy(password) {
|
|
16
25
|
let pool = 0;
|
|
17
26
|
if (/[a-z]/.test(password))
|
|
@@ -22,8 +31,14 @@ export function estimatePasswordEntropy(password) {
|
|
|
22
31
|
pool += 10;
|
|
23
32
|
if (/[^A-Za-z0-9]/.test(password))
|
|
24
33
|
pool += 32;
|
|
34
|
+
// If no characters matched, fallback to 1 to avoid log2(0)
|
|
35
|
+
if (pool === 0)
|
|
36
|
+
pool = 1;
|
|
25
37
|
return password.length * Math.log2(pool);
|
|
26
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalize password string to a consistent form
|
|
41
|
+
*/
|
|
27
42
|
export function normalizePassword(password) {
|
|
28
43
|
return password.normalize('NFKC');
|
|
29
44
|
}
|
|
@@ -7,11 +7,11 @@ export const verifyPassword = async (password, hash) => {
|
|
|
7
7
|
try {
|
|
8
8
|
const result = await bcrypt.compare(password, hash);
|
|
9
9
|
if (!result)
|
|
10
|
-
throw new UnauthorizedError('Password verification failed');
|
|
10
|
+
throw new UnauthorizedError({ message: 'Password verification failed' });
|
|
11
11
|
return result;
|
|
12
12
|
}
|
|
13
13
|
catch {
|
|
14
|
-
throw new UnauthorizedError('Password verification failed');
|
|
14
|
+
throw new UnauthorizedError({ message: 'Password verification failed' });
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
export async function verifyPasswordWithPepper(password, pepper, hash) {
|
|
@@ -24,11 +24,11 @@ export const verifyPasswordSync = (password, hash) => {
|
|
|
24
24
|
try {
|
|
25
25
|
const result = bcrypt.compareSync(password, hash);
|
|
26
26
|
if (!result)
|
|
27
|
-
throw new UnauthorizedError('Password verification failed');
|
|
27
|
+
throw new UnauthorizedError({ message: 'Password verification failed' });
|
|
28
28
|
return result;
|
|
29
29
|
}
|
|
30
|
-
catch (
|
|
31
|
-
throw new UnauthorizedError('Password verification failed');
|
|
30
|
+
catch (_error) {
|
|
31
|
+
throw new UnauthorizedError({ message: 'Password verification failed' });
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
export async function verifyPasswordWithPepperSync(password, pepper, hash) {
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -21,16 +21,11 @@ declare const _default: {
|
|
|
21
21
|
generateTokens: (payload: Record<string, unknown>, accessSecret: import("node_modules/@types/jsonwebtoken").Secret, refreshSecret: import("node_modules/@types/jsonwebtoken").Secret, accessExpiry?: string | number, refreshExpiry?: string | number) => JWTUtils.TokenPair;
|
|
22
22
|
parseDuration(input: string | number): number;
|
|
23
23
|
signToken: (payload: Record<string, unknown>, secret: import("node_modules/@types/jsonwebtoken").Secret, expiresIn?: string | number, options?: import("node_modules/@types/jsonwebtoken").SignOptions) => string;
|
|
24
|
-
validateTokenPayload(payload: Record<string, unknown>, rules?: JWTUtils.TokenRequirements):
|
|
25
|
-
valid: true;
|
|
26
|
-
} | {
|
|
27
|
-
valid: false;
|
|
28
|
-
error: string;
|
|
29
|
-
};
|
|
24
|
+
validateTokenPayload(payload: Record<string, unknown>, rules?: JWTUtils.TokenRequirements): void;
|
|
30
25
|
isTokenExpired(payload: import("node_modules/@types/jsonwebtoken").JwtPayload): boolean;
|
|
31
26
|
verifyToken: (token: string, secret: import("node_modules/@types/jsonwebtoken").Secret) => string | import("node_modules/@types/jsonwebtoken").JwtPayload;
|
|
32
|
-
safeVerifyToken: (token: string, secret: import("node_modules/@types/jsonwebtoken").Secret) => JWTUtils.VerificationResult;
|
|
33
27
|
verifyTokenWithOptions: (token: string, secret: import("node_modules/@types/jsonwebtoken").Secret, options?: import("node_modules/@types/jsonwebtoken").VerifyOptions) => string | import("node_modules/@types/jsonwebtoken").JwtPayload;
|
|
28
|
+
safeVerifyToken: (token: string, secret: import("node_modules/@types/jsonwebtoken").Secret) => JWTUtils.VerificationResult;
|
|
34
29
|
safeVerifyTokenWithOptions: (token: string, secret: import("node_modules/@types/jsonwebtoken").Secret, options?: import("node_modules/@types/jsonwebtoken").VerifyOptions) => JWTUtils.VerificationResult;
|
|
35
30
|
hashPasswordWithPepper(password: string, pepper: string): Promise<string>;
|
|
36
31
|
hashPasswordWithPepperSync(password: string, pepper: string): string;
|
|
@@ -24,7 +24,7 @@ export declare class CryptoManager {
|
|
|
24
24
|
/**
|
|
25
25
|
* Encrypt data using the default or specified algorithm
|
|
26
26
|
*/
|
|
27
|
-
encrypt(plaintext: string, key: string,
|
|
27
|
+
encrypt(plaintext: string, key: string, _options?: {
|
|
28
28
|
algorithm?: string;
|
|
29
29
|
encoding?: BufferEncoding;
|
|
30
30
|
iv?: string;
|
|
@@ -32,7 +32,7 @@ export declare class CryptoManager {
|
|
|
32
32
|
/**
|
|
33
33
|
* Decrypt data using the default or specified algorithm
|
|
34
34
|
*/
|
|
35
|
-
decrypt(encryptedData: string, key: string,
|
|
35
|
+
decrypt(encryptedData: string, key: string, _options?: {
|
|
36
36
|
algorithm?: string;
|
|
37
37
|
encoding?: BufferEncoding;
|
|
38
38
|
iv?: string;
|
|
@@ -40,18 +40,18 @@ export declare class CryptoManager {
|
|
|
40
40
|
/**
|
|
41
41
|
* Generate HMAC signature
|
|
42
42
|
*/
|
|
43
|
-
generateHmac(data: string, secret: string,
|
|
43
|
+
generateHmac(data: string, secret: string, _options?: {
|
|
44
44
|
algorithm?: string;
|
|
45
45
|
encoding?: BufferEncoding;
|
|
46
46
|
}): string;
|
|
47
47
|
/**
|
|
48
48
|
* Generate cryptographically secure random bytes
|
|
49
49
|
*/
|
|
50
|
-
generateSecureRandom(length: number,
|
|
50
|
+
generateSecureRandom(length: number, _encoding?: BufferEncoding): string;
|
|
51
51
|
/**
|
|
52
52
|
* Verify HMAC signature
|
|
53
53
|
*/
|
|
54
|
-
verifyHmac(data: string, secret: string, signature: string,
|
|
54
|
+
verifyHmac(data: string, secret: string, signature: string, _options?: {
|
|
55
55
|
algorithm?: string;
|
|
56
56
|
encoding?: BufferEncoding;
|
|
57
57
|
}): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Secret } from 'jsonwebtoken';
|
|
2
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;
|
|
@@ -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
|
}
|
|
@@ -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;
|