@naman_deep_singh/security 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -355
- package/dist/cjs/core/crypto/cryptoManager.js +34 -17
- package/dist/cjs/core/jwt/decode.js +4 -1
- package/dist/cjs/core/jwt/generateTokens.js +4 -1
- package/dist/cjs/core/jwt/jwtManager.d.ts +19 -43
- package/dist/cjs/core/jwt/jwtManager.js +72 -206
- package/dist/cjs/core/jwt/parseDuration.js +3 -2
- package/dist/cjs/core/jwt/signToken.js +2 -1
- package/dist/cjs/core/jwt/validateToken.d.ts +10 -7
- package/dist/cjs/core/jwt/validateToken.js +14 -11
- package/dist/cjs/core/jwt/verify.d.ts +9 -10
- package/dist/cjs/core/jwt/verify.js +57 -14
- package/dist/cjs/core/password/hash.js +2 -2
- package/dist/cjs/core/password/passwordManager.d.ts +1 -1
- package/dist/cjs/core/password/passwordManager.js +35 -87
- package/dist/cjs/core/password/strength.js +5 -5
- package/dist/cjs/core/password/utils.d.ts +12 -0
- package/dist/cjs/core/password/utils.js +16 -1
- package/dist/cjs/core/password/verify.js +4 -4
- package/dist/cjs/index.d.ts +2 -7
- package/dist/esm/core/crypto/cryptoManager.js +34 -17
- package/dist/esm/core/jwt/decode.js +4 -1
- package/dist/esm/core/jwt/generateTokens.js +4 -1
- package/dist/esm/core/jwt/jwtManager.d.ts +19 -43
- package/dist/esm/core/jwt/jwtManager.js +73 -207
- package/dist/esm/core/jwt/parseDuration.js +3 -2
- package/dist/esm/core/jwt/signToken.js +2 -1
- package/dist/esm/core/jwt/validateToken.d.ts +10 -7
- package/dist/esm/core/jwt/validateToken.js +14 -11
- package/dist/esm/core/jwt/verify.d.ts +9 -10
- package/dist/esm/core/jwt/verify.js +55 -12
- package/dist/esm/core/password/hash.js +2 -2
- package/dist/esm/core/password/passwordManager.d.ts +1 -1
- package/dist/esm/core/password/passwordManager.js +35 -87
- package/dist/esm/core/password/strength.js +5 -5
- package/dist/esm/core/password/utils.d.ts +12 -0
- package/dist/esm/core/password/utils.js +16 -1
- package/dist/esm/core/password/verify.js +4 -4
- package/dist/esm/index.d.ts +2 -7
- package/dist/types/core/jwt/jwtManager.d.ts +19 -43
- package/dist/types/core/jwt/validateToken.d.ts +10 -7
- package/dist/types/core/jwt/verify.d.ts +9 -10
- package/dist/types/core/password/passwordManager.d.ts +1 -1
- package/dist/types/core/password/utils.d.ts +12 -0
- package/dist/types/index.d.ts +2 -7
- package/package.json +2 -2
|
@@ -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;
|
|
@@ -19,7 +19,7 @@ const hashPassword = async (password, saltRounds = 10) => {
|
|
|
19
19
|
return bcryptjs_1.default.hash(password, salt);
|
|
20
20
|
}
|
|
21
21
|
catch (_err) {
|
|
22
|
-
throw new errors_utils_1.InternalServerError('Password hashing failed');
|
|
22
|
+
throw new errors_utils_1.InternalServerError({ message: 'Password hashing failed' });
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
exports.hashPassword = hashPassword;
|
|
@@ -36,7 +36,7 @@ const hashPasswordSync = (password, saltRounds = 10) => {
|
|
|
36
36
|
return bcryptjs_1.default.hashSync(password, salt);
|
|
37
37
|
}
|
|
38
38
|
catch (_error) {
|
|
39
|
-
throw new errors_utils_1.InternalServerError('Password hashing failed');
|
|
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
28
|
needsUpgrade(_hash: string, _currentConfig: PasswordConfig): boolean;
|
|
29
29
|
}
|
|
@@ -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,26 +124,20 @@ class PasswordManager {
|
|
|
158
124
|
let score = 0;
|
|
159
125
|
const feedback = [];
|
|
160
126
|
const suggestions = [];
|
|
161
|
-
/* ---------------- Entropy baseline ---------------- */
|
|
162
127
|
if (entropy < 28) {
|
|
163
128
|
feedback.push('Password is easy to guess');
|
|
164
129
|
suggestions.push('Use more unique characters and length');
|
|
165
130
|
}
|
|
166
|
-
else if (entropy < 36)
|
|
167
|
-
score
|
|
168
|
-
|
|
169
|
-
else if (entropy < 60) {
|
|
131
|
+
else if (entropy < 36)
|
|
132
|
+
score++;
|
|
133
|
+
else if (entropy < 60)
|
|
170
134
|
score += 2;
|
|
171
|
-
|
|
172
|
-
else {
|
|
135
|
+
else
|
|
173
136
|
score += 3;
|
|
174
|
-
}
|
|
175
|
-
/* ---------------- Length scoring ---------------- */
|
|
176
137
|
if (password.length >= 12)
|
|
177
138
|
score++;
|
|
178
139
|
if (password.length >= 16)
|
|
179
140
|
score++;
|
|
180
|
-
/* ---------------- Character variety ---------------- */
|
|
181
141
|
if (/[a-z]/.test(password))
|
|
182
142
|
score++;
|
|
183
143
|
if (/[A-Z]/.test(password))
|
|
@@ -186,7 +146,6 @@ class PasswordManager {
|
|
|
186
146
|
score++;
|
|
187
147
|
if (/[^A-Za-z0-9]/.test(password))
|
|
188
148
|
score++;
|
|
189
|
-
/* ---------------- Pattern deductions ---------------- */
|
|
190
149
|
if (/^[A-Za-z]+$/.test(password)) {
|
|
191
150
|
score--;
|
|
192
151
|
feedback.push('Consider adding numbers or symbols');
|
|
@@ -203,15 +162,12 @@ class PasswordManager {
|
|
|
203
162
|
score--;
|
|
204
163
|
feedback.push('Avoid sequential patterns');
|
|
205
164
|
}
|
|
206
|
-
/* ---------------- Common passwords ---------------- */
|
|
207
165
|
const commonPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
|
|
208
166
|
if (commonPasswords.some((common) => password.toLowerCase().includes(common))) {
|
|
209
167
|
score = 0;
|
|
210
168
|
feedback.push('Avoid common passwords');
|
|
211
169
|
}
|
|
212
|
-
/* ---------------- Clamp score ---------------- */
|
|
213
170
|
score = Math.max(0, Math.min(4, score));
|
|
214
|
-
/* ---------------- Strength label ---------------- */
|
|
215
171
|
let label;
|
|
216
172
|
switch (score) {
|
|
217
173
|
case 0:
|
|
@@ -234,22 +190,14 @@ class PasswordManager {
|
|
|
234
190
|
label = 'strong';
|
|
235
191
|
suggestions.push('Your password is very secure');
|
|
236
192
|
break;
|
|
237
|
-
default:
|
|
238
|
-
label = 'very-weak';
|
|
193
|
+
default: label = 'very-weak';
|
|
239
194
|
}
|
|
240
|
-
return {
|
|
241
|
-
score,
|
|
242
|
-
label,
|
|
243
|
-
feedback,
|
|
244
|
-
suggestions,
|
|
245
|
-
};
|
|
195
|
+
return { score, label, feedback, suggestions };
|
|
246
196
|
}
|
|
247
197
|
/**
|
|
248
|
-
* Check if password hash needs upgrade (
|
|
198
|
+
* Check if password hash needs upgrade (saltRounds change)
|
|
249
199
|
*/
|
|
250
200
|
needsUpgrade(_hash, _currentConfig) {
|
|
251
|
-
// Simple heuristic: if the hash doesn't match current salt rounds pattern
|
|
252
|
-
// In practice, you'd need to store the salt rounds with the hash
|
|
253
201
|
return false;
|
|
254
202
|
}
|
|
255
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
39
|
catch (_error) {
|
|
40
|
-
throw new errors_utils_1.UnauthorizedError('Password verification failed');
|
|
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;
|