@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
|
@@ -27,25 +27,20 @@ class PasswordManager {
|
|
|
27
27
|
async hash(password, salt) {
|
|
28
28
|
try {
|
|
29
29
|
(0, utils_1.ensureValidPassword)(password);
|
|
30
|
-
// Validate password meets basic requirements
|
|
31
30
|
this.validate(password);
|
|
32
31
|
const saltRounds = this.defaultConfig.saltRounds;
|
|
33
|
-
let
|
|
34
|
-
if (!
|
|
35
|
-
|
|
32
|
+
let finalSalt = salt;
|
|
33
|
+
if (!finalSalt) {
|
|
34
|
+
finalSalt = await bcryptjs_1.default.genSalt(saltRounds);
|
|
36
35
|
}
|
|
37
|
-
const hash = await bcryptjs_1.default.hash(password,
|
|
38
|
-
return {
|
|
39
|
-
hash,
|
|
40
|
-
salt: passwordSalt,
|
|
41
|
-
};
|
|
36
|
+
const hash = await bcryptjs_1.default.hash(password, finalSalt);
|
|
37
|
+
return { hash, salt: finalSalt };
|
|
42
38
|
}
|
|
43
39
|
catch (error) {
|
|
44
|
-
if (error instanceof errors_utils_1.BadRequestError ||
|
|
45
|
-
error instanceof errors_utils_1.ValidationError) {
|
|
40
|
+
if (error instanceof errors_utils_1.BadRequestError || error instanceof errors_utils_1.ValidationError) {
|
|
46
41
|
throw error;
|
|
47
42
|
}
|
|
48
|
-
throw new errors_utils_1.BadRequestError('Failed to hash password');
|
|
43
|
+
throw new errors_utils_1.BadRequestError({ message: 'Failed to hash password' }, error instanceof Error ? error : undefined);
|
|
49
44
|
}
|
|
50
45
|
}
|
|
51
46
|
/**
|
|
@@ -53,19 +48,12 @@ class PasswordManager {
|
|
|
53
48
|
*/
|
|
54
49
|
async verify(password, hash, salt) {
|
|
55
50
|
try {
|
|
56
|
-
if (!password || !hash || !salt)
|
|
51
|
+
if (!password || !hash || !salt)
|
|
57
52
|
return false;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const isValid = await bcryptjs_1.default.compare(password, hash);
|
|
61
|
-
// If invalid and different salt was used, try regenerating hash with new salt
|
|
62
|
-
if (!isValid && salt !== this.defaultConfig.saltRounds?.toString()) {
|
|
63
|
-
const newHash = await bcryptjs_1.default.hash(password, salt);
|
|
64
|
-
return newHash === hash;
|
|
65
|
-
}
|
|
66
|
-
return isValid;
|
|
53
|
+
// bcrypt compare works directly with hash
|
|
54
|
+
return await bcryptjs_1.default.compare(password, hash);
|
|
67
55
|
}
|
|
68
|
-
catch
|
|
56
|
+
catch {
|
|
69
57
|
return false;
|
|
70
58
|
}
|
|
71
59
|
}
|
|
@@ -75,7 +63,7 @@ class PasswordManager {
|
|
|
75
63
|
generate(length = 16, options = {}) {
|
|
76
64
|
const config = { ...this.defaultConfig, ...options };
|
|
77
65
|
if (length < config.minLength || length > config.maxLength) {
|
|
78
|
-
throw new errors_utils_1.ValidationError(`Password length must be between ${config.minLength} and ${config.maxLength}`);
|
|
66
|
+
throw new errors_utils_1.ValidationError({ message: `Password length must be between ${config.minLength} and ${config.maxLength}` });
|
|
79
67
|
}
|
|
80
68
|
let charset = 'abcdefghijklmnopqrstuvwxyz';
|
|
81
69
|
if (config.requireUppercase)
|
|
@@ -84,24 +72,20 @@ class PasswordManager {
|
|
|
84
72
|
charset += '0123456789';
|
|
85
73
|
if (config.requireSpecialChars)
|
|
86
74
|
charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
87
|
-
let password = '';
|
|
88
75
|
const randomBytes = crypto_1.default.randomBytes(length);
|
|
76
|
+
let password = '';
|
|
89
77
|
for (let i = 0; i < length; i++) {
|
|
90
78
|
password += charset[randomBytes[i] % charset.length];
|
|
91
79
|
}
|
|
92
|
-
// Ensure
|
|
93
|
-
if (config.requireUppercase && !/[A-Z]/.test(password))
|
|
80
|
+
// Ensure requirements
|
|
81
|
+
if (config.requireUppercase && !/[A-Z]/.test(password))
|
|
94
82
|
password = password.replace(/[a-z]/, 'A');
|
|
95
|
-
|
|
96
|
-
if (config.requireLowercase && !/[a-z]/.test(password)) {
|
|
83
|
+
if (config.requireLowercase && !/[a-z]/.test(password))
|
|
97
84
|
password = password.replace(/[A-Z]/, 'a');
|
|
98
|
-
|
|
99
|
-
if (config.requireNumbers && !/[0-9]/.test(password)) {
|
|
85
|
+
if (config.requireNumbers && !/[0-9]/.test(password))
|
|
100
86
|
password = password.replace(/[A-Za-z]/, '0');
|
|
101
|
-
|
|
102
|
-
if (config.requireSpecialChars && !/[^A-Za-z0-9]/.test(password)) {
|
|
87
|
+
if (config.requireSpecialChars && !/[^A-Za-z0-9]/.test(password))
|
|
103
88
|
password = password.replace(/[A-Za-z0-9]/, '!');
|
|
104
|
-
}
|
|
105
89
|
return password;
|
|
106
90
|
}
|
|
107
91
|
/**
|
|
@@ -110,45 +94,27 @@ class PasswordManager {
|
|
|
110
94
|
validate(password, config = {}) {
|
|
111
95
|
const finalConfig = { ...this.defaultConfig, ...config };
|
|
112
96
|
const errors = [];
|
|
113
|
-
|
|
114
|
-
if (!password || typeof password !== 'string') {
|
|
97
|
+
if (!password || typeof password !== 'string')
|
|
115
98
|
errors.push('Password must be a non-empty string');
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (password.length
|
|
119
|
-
errors.push(`Password must be at least ${finalConfig.minLength} characters long`);
|
|
120
|
-
}
|
|
121
|
-
if (password.length > finalConfig.maxLength) {
|
|
99
|
+
if (password.length < finalConfig.minLength)
|
|
100
|
+
errors.push(`Password must be at least ${finalConfig.minLength} characters`);
|
|
101
|
+
if (password.length > finalConfig.maxLength)
|
|
122
102
|
errors.push(`Password must not exceed ${finalConfig.maxLength} characters`);
|
|
123
|
-
|
|
124
|
-
// Complexity requirements
|
|
125
|
-
if (finalConfig.requireUppercase && !/[A-Z]/.test(password)) {
|
|
103
|
+
if (finalConfig.requireUppercase && !/[A-Z]/.test(password))
|
|
126
104
|
errors.push('Password must contain at least one uppercase letter');
|
|
127
|
-
|
|
128
|
-
if (finalConfig.requireLowercase && !/[a-z]/.test(password)) {
|
|
105
|
+
if (finalConfig.requireLowercase && !/[a-z]/.test(password))
|
|
129
106
|
errors.push('Password must contain at least one lowercase letter');
|
|
130
|
-
|
|
131
|
-
if (finalConfig.requireNumbers && !/[0-9]/.test(password)) {
|
|
107
|
+
if (finalConfig.requireNumbers && !/[0-9]/.test(password))
|
|
132
108
|
errors.push('Password must contain at least one number');
|
|
133
|
-
|
|
134
|
-
if (finalConfig.requireSpecialChars && !/[^A-Za-z0-9]/.test(password)) {
|
|
109
|
+
if (finalConfig.requireSpecialChars && !/[^A-Za-z0-9]/.test(password))
|
|
135
110
|
errors.push('Password must contain at least one special character');
|
|
136
|
-
}
|
|
137
|
-
// Custom rules
|
|
138
111
|
if (finalConfig.customRules) {
|
|
139
112
|
finalConfig.customRules.forEach((rule) => {
|
|
140
|
-
if (!rule.test(password))
|
|
113
|
+
if (!rule.test(password))
|
|
141
114
|
errors.push(rule.message);
|
|
142
|
-
}
|
|
143
115
|
});
|
|
144
116
|
}
|
|
145
|
-
|
|
146
|
-
const isValid = errors.length === 0;
|
|
147
|
-
return {
|
|
148
|
-
isValid,
|
|
149
|
-
errors,
|
|
150
|
-
strength,
|
|
151
|
-
};
|
|
117
|
+
return { isValid: errors.length === 0, errors, strength: this.checkStrength(password) };
|
|
152
118
|
}
|
|
153
119
|
/**
|
|
154
120
|
* Check password strength
|
|
@@ -158,14 +124,20 @@ class PasswordManager {
|
|
|
158
124
|
let score = 0;
|
|
159
125
|
const feedback = [];
|
|
160
126
|
const suggestions = [];
|
|
161
|
-
|
|
162
|
-
|
|
127
|
+
if (entropy < 28) {
|
|
128
|
+
feedback.push('Password is easy to guess');
|
|
129
|
+
suggestions.push('Use more unique characters and length');
|
|
130
|
+
}
|
|
131
|
+
else if (entropy < 36)
|
|
163
132
|
score++;
|
|
133
|
+
else if (entropy < 60)
|
|
134
|
+
score += 2;
|
|
135
|
+
else
|
|
136
|
+
score += 3;
|
|
164
137
|
if (password.length >= 12)
|
|
165
138
|
score++;
|
|
166
139
|
if (password.length >= 16)
|
|
167
140
|
score++;
|
|
168
|
-
// Character variety scoring
|
|
169
141
|
if (/[a-z]/.test(password))
|
|
170
142
|
score++;
|
|
171
143
|
if (/[A-Z]/.test(password))
|
|
@@ -174,10 +146,9 @@ class PasswordManager {
|
|
|
174
146
|
score++;
|
|
175
147
|
if (/[^A-Za-z0-9]/.test(password))
|
|
176
148
|
score++;
|
|
177
|
-
// Common patterns deduction
|
|
178
149
|
if (/^[A-Za-z]+$/.test(password)) {
|
|
179
150
|
score--;
|
|
180
|
-
feedback.push('Consider adding numbers
|
|
151
|
+
feedback.push('Consider adding numbers or symbols');
|
|
181
152
|
}
|
|
182
153
|
if (/^[0-9]+$/.test(password)) {
|
|
183
154
|
score -= 2;
|
|
@@ -191,13 +162,11 @@ class PasswordManager {
|
|
|
191
162
|
score--;
|
|
192
163
|
feedback.push('Avoid sequential patterns');
|
|
193
164
|
}
|
|
194
|
-
// Common passwords check
|
|
195
165
|
const commonPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
|
|
196
166
|
if (commonPasswords.some((common) => password.toLowerCase().includes(common))) {
|
|
197
167
|
score = 0;
|
|
198
168
|
feedback.push('Avoid common passwords');
|
|
199
169
|
}
|
|
200
|
-
// Clamp score and determine label
|
|
201
170
|
score = Math.max(0, Math.min(4, score));
|
|
202
171
|
let label;
|
|
203
172
|
switch (score) {
|
|
@@ -211,7 +180,7 @@ class PasswordManager {
|
|
|
211
180
|
break;
|
|
212
181
|
case 2:
|
|
213
182
|
label = 'fair';
|
|
214
|
-
suggestions.push('Consider
|
|
183
|
+
suggestions.push('Consider increasing length or randomness');
|
|
215
184
|
break;
|
|
216
185
|
case 3:
|
|
217
186
|
label = 'good';
|
|
@@ -221,22 +190,14 @@ class PasswordManager {
|
|
|
221
190
|
label = 'strong';
|
|
222
191
|
suggestions.push('Your password is very secure');
|
|
223
192
|
break;
|
|
224
|
-
default:
|
|
225
|
-
label = 'very-weak';
|
|
193
|
+
default: label = 'very-weak';
|
|
226
194
|
}
|
|
227
|
-
return {
|
|
228
|
-
score,
|
|
229
|
-
label,
|
|
230
|
-
feedback,
|
|
231
|
-
suggestions,
|
|
232
|
-
};
|
|
195
|
+
return { score, label, feedback, suggestions };
|
|
233
196
|
}
|
|
234
197
|
/**
|
|
235
|
-
* Check if password hash needs upgrade (
|
|
198
|
+
* Check if password hash needs upgrade (saltRounds change)
|
|
236
199
|
*/
|
|
237
|
-
needsUpgrade(
|
|
238
|
-
// Simple heuristic: if the hash doesn't match current salt rounds pattern
|
|
239
|
-
// In practice, you'd need to store the salt rounds with the hash
|
|
200
|
+
needsUpgrade(_hash, _currentConfig) {
|
|
240
201
|
return false;
|
|
241
202
|
}
|
|
242
203
|
}
|
|
@@ -4,18 +4,18 @@ exports.isPasswordStrong = void 0;
|
|
|
4
4
|
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
5
5
|
const isPasswordStrong = (password, options = {}) => {
|
|
6
6
|
if (!password)
|
|
7
|
-
throw new errors_utils_1.BadRequestError('Invalid password provided');
|
|
7
|
+
throw new errors_utils_1.BadRequestError({ message: 'Invalid password provided' });
|
|
8
8
|
const { minLength = 8, requireUppercase = true, requireLowercase = true, requireNumbers = true, requireSymbols = false, } = options;
|
|
9
9
|
if (password.length < minLength)
|
|
10
10
|
throw new errors_utils_1.ValidationError(`Password must be at least ${minLength} characters`);
|
|
11
11
|
if (requireUppercase && !/[A-Z]/.test(password))
|
|
12
|
-
throw new errors_utils_1.ValidationError('Password must include uppercase letters');
|
|
12
|
+
throw new errors_utils_1.ValidationError({ message: 'Password must include uppercase letters' });
|
|
13
13
|
if (requireLowercase && !/[a-z]/.test(password))
|
|
14
|
-
throw new errors_utils_1.ValidationError('Password must include lowercase letters');
|
|
14
|
+
throw new errors_utils_1.ValidationError({ message: 'Password must include lowercase letters' });
|
|
15
15
|
if (requireNumbers && !/[0-9]/.test(password))
|
|
16
|
-
throw new errors_utils_1.ValidationError('Password must include numbers');
|
|
16
|
+
throw new errors_utils_1.ValidationError({ message: 'Password must include numbers' });
|
|
17
17
|
if (requireSymbols && !/[^A-Za-z0-9]/.test(password))
|
|
18
|
-
throw new errors_utils_1.ValidationError('Password must include symbols');
|
|
18
|
+
throw new errors_utils_1.ValidationError({ message: 'Password must include symbols' });
|
|
19
19
|
return true;
|
|
20
20
|
};
|
|
21
21
|
exports.isPasswordStrong = isPasswordStrong;
|
|
@@ -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;
|
|
@@ -9,11 +9,17 @@ exports.estimatePasswordEntropy = estimatePasswordEntropy;
|
|
|
9
9
|
exports.normalizePassword = normalizePassword;
|
|
10
10
|
const crypto_1 = __importDefault(require("crypto"));
|
|
11
11
|
const errors_utils_1 = require("@naman_deep_singh/errors-utils");
|
|
12
|
+
/**
|
|
13
|
+
* Ensure password is a valid non-empty string
|
|
14
|
+
*/
|
|
12
15
|
function ensureValidPassword(password) {
|
|
13
16
|
if (!password || typeof password !== 'string') {
|
|
14
|
-
throw new errors_utils_1.BadRequestError('Invalid password provided');
|
|
17
|
+
throw new errors_utils_1.BadRequestError({ message: 'Invalid password provided' });
|
|
15
18
|
}
|
|
16
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Timing-safe comparison between two strings
|
|
22
|
+
*/
|
|
17
23
|
function safeCompare(a, b) {
|
|
18
24
|
const bufA = Buffer.from(a);
|
|
19
25
|
const bufB = Buffer.from(b);
|
|
@@ -21,6 +27,9 @@ function safeCompare(a, b) {
|
|
|
21
27
|
return false;
|
|
22
28
|
return crypto_1.default.timingSafeEqual(bufA, bufB);
|
|
23
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Estimate password entropy based on character pool
|
|
32
|
+
*/
|
|
24
33
|
function estimatePasswordEntropy(password) {
|
|
25
34
|
let pool = 0;
|
|
26
35
|
if (/[a-z]/.test(password))
|
|
@@ -31,8 +40,14 @@ function estimatePasswordEntropy(password) {
|
|
|
31
40
|
pool += 10;
|
|
32
41
|
if (/[^A-Za-z0-9]/.test(password))
|
|
33
42
|
pool += 32;
|
|
43
|
+
// If no characters matched, fallback to 1 to avoid log2(0)
|
|
44
|
+
if (pool === 0)
|
|
45
|
+
pool = 1;
|
|
34
46
|
return password.length * Math.log2(pool);
|
|
35
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Normalize password string to a consistent form
|
|
50
|
+
*/
|
|
36
51
|
function normalizePassword(password) {
|
|
37
52
|
return password.normalize('NFKC');
|
|
38
53
|
}
|
|
@@ -15,11 +15,11 @@ const verifyPassword = async (password, hash) => {
|
|
|
15
15
|
try {
|
|
16
16
|
const result = await bcryptjs_1.default.compare(password, hash);
|
|
17
17
|
if (!result)
|
|
18
|
-
throw new errors_utils_1.UnauthorizedError('Password verification failed');
|
|
18
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Password verification failed' });
|
|
19
19
|
return result;
|
|
20
20
|
}
|
|
21
21
|
catch {
|
|
22
|
-
throw new errors_utils_1.UnauthorizedError('Password verification failed');
|
|
22
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Password verification failed' });
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
exports.verifyPassword = verifyPassword;
|
|
@@ -33,11 +33,11 @@ const verifyPasswordSync = (password, hash) => {
|
|
|
33
33
|
try {
|
|
34
34
|
const result = bcryptjs_1.default.compareSync(password, hash);
|
|
35
35
|
if (!result)
|
|
36
|
-
throw new errors_utils_1.UnauthorizedError('Password verification failed');
|
|
36
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Password verification failed' });
|
|
37
37
|
return result;
|
|
38
38
|
}
|
|
39
|
-
catch (
|
|
40
|
-
throw new errors_utils_1.UnauthorizedError('Password verification failed');
|
|
39
|
+
catch (_error) {
|
|
40
|
+
throw new errors_utils_1.UnauthorizedError({ message: 'Password verification failed' });
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
exports.verifyPasswordSync = verifyPasswordSync;
|
package/dist/cjs/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,3 +1,4 @@
|
|
|
1
|
+
import { InternalServerError } from '@naman_deep_singh/errors-utils';
|
|
1
2
|
import { decrypt as functionalDecrypt, encrypt as functionalEncrypt, hmacSign as functionalHmacSign, hmacVerify as functionalHmacVerify, randomToken as functionalRandomToken, } from './index';
|
|
2
3
|
/**
|
|
3
4
|
* Default configuration
|
|
@@ -30,23 +31,33 @@ export class CryptoManager {
|
|
|
30
31
|
/**
|
|
31
32
|
* Encrypt data using the default or specified algorithm
|
|
32
33
|
*/
|
|
33
|
-
encrypt(plaintext, key,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
encrypt(plaintext, key, _options) {
|
|
35
|
+
try {
|
|
36
|
+
return functionalEncrypt(plaintext, key);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
throw new InternalServerError(undefined, {
|
|
40
|
+
message: 'Encryption failed',
|
|
41
|
+
}, err instanceof Error ? err : undefined);
|
|
42
|
+
}
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
45
|
* Decrypt data using the default or specified algorithm
|
|
40
46
|
*/
|
|
41
|
-
decrypt(encryptedData, key,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
decrypt(encryptedData, key, _options) {
|
|
48
|
+
try {
|
|
49
|
+
return functionalDecrypt(encryptedData, key);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
throw new InternalServerError(undefined, {
|
|
53
|
+
message: 'Decryption failed',
|
|
54
|
+
}, err instanceof Error ? err : undefined);
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
/**
|
|
47
58
|
* Generate HMAC signature
|
|
48
59
|
*/
|
|
49
|
-
generateHmac(data, secret,
|
|
60
|
+
generateHmac(data, secret, _options) {
|
|
50
61
|
// Use the basic HMAC sign function for now
|
|
51
62
|
// TODO: Add support for different algorithms
|
|
52
63
|
return functionalHmacSign(data, secret);
|
|
@@ -54,14 +65,14 @@ export class CryptoManager {
|
|
|
54
65
|
/**
|
|
55
66
|
* Generate cryptographically secure random bytes
|
|
56
67
|
*/
|
|
57
|
-
generateSecureRandom(length,
|
|
68
|
+
generateSecureRandom(length, _encoding = 'hex') {
|
|
58
69
|
// Use the basic random token function
|
|
59
70
|
return functionalRandomToken(length);
|
|
60
71
|
}
|
|
61
72
|
/**
|
|
62
73
|
* Verify HMAC signature
|
|
63
74
|
*/
|
|
64
|
-
verifyHmac(data, secret, signature,
|
|
75
|
+
verifyHmac(data, secret, signature, _options) {
|
|
65
76
|
// Use the basic HMAC verify function
|
|
66
77
|
return functionalHmacVerify(data, secret, signature);
|
|
67
78
|
}
|
|
@@ -73,7 +84,9 @@ export class CryptoManager {
|
|
|
73
84
|
const crypto = require('crypto');
|
|
74
85
|
crypto.pbkdf2(password, salt, iterations, keyLength, 'sha256', (err, derivedKey) => {
|
|
75
86
|
if (err) {
|
|
76
|
-
reject(
|
|
87
|
+
reject(new InternalServerError(undefined, {
|
|
88
|
+
message: 'Key derivation failed',
|
|
89
|
+
}, err instanceof Error ? err : undefined));
|
|
77
90
|
}
|
|
78
91
|
else {
|
|
79
92
|
resolve(derivedKey.toString('hex'));
|
|
@@ -99,7 +112,7 @@ export class CryptoManager {
|
|
|
99
112
|
* Generate a secure key pair for asymmetric encryption
|
|
100
113
|
*/
|
|
101
114
|
generateKeyPair(options) {
|
|
102
|
-
return new Promise((resolve,
|
|
115
|
+
return new Promise((resolve, _reject) => {
|
|
103
116
|
const crypto = require('crypto');
|
|
104
117
|
const keyPair = crypto.generateKeyPairSync('rsa', {
|
|
105
118
|
modulusLength: options?.modulusLength || 2048,
|
|
@@ -119,7 +132,7 @@ export class CryptoManager {
|
|
|
119
132
|
* Encrypt data using RSA public key
|
|
120
133
|
*/
|
|
121
134
|
rsaEncrypt(data, publicKey) {
|
|
122
|
-
return new Promise((resolve,
|
|
135
|
+
return new Promise((resolve, _reject) => {
|
|
123
136
|
const crypto = require('crypto');
|
|
124
137
|
const buffer = Buffer.from(data, 'utf8');
|
|
125
138
|
const encrypted = crypto.publicEncrypt(publicKey, buffer);
|
|
@@ -130,7 +143,7 @@ export class CryptoManager {
|
|
|
130
143
|
* Decrypt data using RSA private key
|
|
131
144
|
*/
|
|
132
145
|
rsaDecrypt(encryptedData, privateKey) {
|
|
133
|
-
return new Promise((resolve,
|
|
146
|
+
return new Promise((resolve, _reject) => {
|
|
134
147
|
const crypto = require('crypto');
|
|
135
148
|
const buffer = Buffer.from(encryptedData, 'base64');
|
|
136
149
|
const decrypted = crypto.privateDecrypt(privateKey, buffer);
|
|
@@ -143,15 +156,17 @@ export class CryptoManager {
|
|
|
143
156
|
rsaSign(data, privateKey, algorithm = 'sha256') {
|
|
144
157
|
return new Promise((resolve, reject) => {
|
|
145
158
|
const crypto = require('crypto');
|
|
146
|
-
const sign = crypto.createSign(algorithm);
|
|
147
|
-
sign.update(data);
|
|
148
|
-
sign.end();
|
|
149
159
|
try {
|
|
160
|
+
const sign = crypto.createSign(algorithm);
|
|
161
|
+
sign.update(data);
|
|
162
|
+
sign.end();
|
|
150
163
|
const signature = sign.sign(privateKey, 'base64');
|
|
151
164
|
resolve(signature);
|
|
152
165
|
}
|
|
153
|
-
catch (
|
|
154
|
-
reject(
|
|
166
|
+
catch (err) {
|
|
167
|
+
reject(new InternalServerError(undefined, {
|
|
168
|
+
message: 'RSA signing failed',
|
|
169
|
+
}, err instanceof Error ? err : undefined));
|
|
155
170
|
}
|
|
156
171
|
});
|
|
157
172
|
}
|
|
@@ -161,15 +176,17 @@ export class CryptoManager {
|
|
|
161
176
|
rsaVerify(data, signature, publicKey, algorithm = 'sha256') {
|
|
162
177
|
return new Promise((resolve, reject) => {
|
|
163
178
|
const crypto = require('crypto');
|
|
164
|
-
const verify = crypto.createVerify(algorithm);
|
|
165
|
-
verify.update(data);
|
|
166
|
-
verify.end();
|
|
167
179
|
try {
|
|
180
|
+
const verify = crypto.createVerify(algorithm);
|
|
181
|
+
verify.update(data);
|
|
182
|
+
verify.end();
|
|
168
183
|
const isValid = verify.verify(publicKey, signature, 'base64');
|
|
169
184
|
resolve(isValid);
|
|
170
185
|
}
|
|
171
|
-
catch (
|
|
172
|
-
reject(
|
|
186
|
+
catch (err) {
|
|
187
|
+
reject(new InternalServerError(undefined, {
|
|
188
|
+
message: 'RSA verification failed',
|
|
189
|
+
}, err instanceof Error ? err : undefined));
|
|
173
190
|
}
|
|
174
191
|
});
|
|
175
192
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/jwt/decodeToken.ts
|
|
2
2
|
import { decode } from 'jsonwebtoken';
|
|
3
|
+
import { BadRequestError } from '@naman_deep_singh/errors-utils';
|
|
3
4
|
/**
|
|
4
5
|
* Flexible decode
|
|
5
6
|
* Returns: null | string | JwtPayload
|
|
@@ -15,7 +16,9 @@ export function decodeToken(token) {
|
|
|
15
16
|
export function decodeTokenStrict(token) {
|
|
16
17
|
const decoded = decode(token);
|
|
17
18
|
if (!decoded || typeof decoded === 'string') {
|
|
18
|
-
throw new
|
|
19
|
+
throw new BadRequestError({
|
|
20
|
+
message: 'Invalid JWT payload structure',
|
|
21
|
+
});
|
|
19
22
|
}
|
|
20
23
|
return decoded;
|
|
21
24
|
}
|
|
@@ -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,9 +1,10 @@
|
|
|
1
1
|
import { signToken } from './signToken';
|
|
2
2
|
import { verifyToken } from './verify';
|
|
3
|
+
import { TokenMalformedError } from '@naman_deep_singh/errors-utils';
|
|
3
4
|
// Helper function to create branded tokens
|
|
4
|
-
const createBrandedToken = (token, _brand) => {
|
|
5
|
-
return token
|
|
6
|
-
}
|
|
5
|
+
/* const createBrandedToken = <T extends string>(token: string, _brand: T): T => {
|
|
6
|
+
return token as T
|
|
7
|
+
} */
|
|
7
8
|
export const generateTokens = (payload, accessSecret, refreshSecret, accessExpiry = '15m', refreshExpiry = '7d') => {
|
|
8
9
|
const accessToken = signToken(payload, accessSecret, accessExpiry, {
|
|
9
10
|
algorithm: 'HS256',
|
|
@@ -19,7 +20,9 @@ export const generateTokens = (payload, accessSecret, refreshSecret, accessExpir
|
|
|
19
20
|
export function rotateRefreshToken(oldToken, secret) {
|
|
20
21
|
const decoded = verifyToken(oldToken, secret);
|
|
21
22
|
if (typeof decoded === 'string') {
|
|
22
|
-
throw new
|
|
23
|
+
throw new TokenMalformedError({
|
|
24
|
+
message: 'Invalid token payload — expected JWT payload object',
|
|
25
|
+
});
|
|
23
26
|
}
|
|
24
27
|
const payload = { ...decoded };
|
|
25
28
|
delete payload.iat;
|