@naman_deep_singh/security 1.2.0 → 1.3.1

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.
Files changed (95) hide show
  1. package/README.md +355 -176
  2. package/dist/cjs/core/crypto/cryptoManager.d.ts +111 -0
  3. package/dist/cjs/core/crypto/cryptoManager.js +191 -0
  4. package/dist/cjs/core/crypto/decrypt.js +6 -6
  5. package/dist/cjs/core/crypto/encrypt.js +4 -4
  6. package/dist/cjs/core/crypto/hmac.js +1 -1
  7. package/dist/cjs/core/crypto/index.d.ts +5 -4
  8. package/dist/cjs/core/crypto/index.js +12 -4
  9. package/dist/cjs/core/crypto/random.js +2 -2
  10. package/dist/cjs/core/jwt/decode.d.ts +1 -1
  11. package/dist/cjs/core/jwt/decode.js +2 -2
  12. package/dist/cjs/core/jwt/extractToken.js +7 -7
  13. package/dist/cjs/core/jwt/generateTokens.d.ts +2 -2
  14. package/dist/cjs/core/jwt/generateTokens.js +10 -6
  15. package/dist/cjs/core/jwt/index.d.ts +8 -8
  16. package/dist/cjs/core/jwt/jwtManager.d.ts +67 -0
  17. package/dist/cjs/core/jwt/jwtManager.js +299 -0
  18. package/dist/cjs/core/jwt/parseDuration.js +3 -3
  19. package/dist/cjs/core/jwt/signToken.d.ts +1 -1
  20. package/dist/cjs/core/jwt/signToken.js +7 -7
  21. package/dist/cjs/core/jwt/types.d.ts +1 -1
  22. package/dist/cjs/core/jwt/validateToken.d.ts +2 -2
  23. package/dist/cjs/core/jwt/validateToken.js +3 -3
  24. package/dist/cjs/core/jwt/verify.d.ts +3 -2
  25. package/dist/cjs/core/password/hash.js +1 -1
  26. package/dist/cjs/core/password/index.d.ts +3 -3
  27. package/dist/cjs/core/password/passwordManager.d.ts +29 -0
  28. package/dist/cjs/core/password/passwordManager.js +243 -0
  29. package/dist/cjs/core/password/strength.d.ts +1 -1
  30. package/dist/cjs/core/password/strength.js +4 -4
  31. package/dist/cjs/core/password/utils.js +2 -2
  32. package/dist/cjs/core/password/verify.js +1 -1
  33. package/dist/cjs/index.d.ts +9 -5
  34. package/dist/cjs/index.js +2 -2
  35. package/dist/cjs/interfaces/jwt.interface.d.ts +47 -0
  36. package/dist/cjs/interfaces/jwt.interface.js +2 -0
  37. package/dist/cjs/interfaces/password.interface.d.ts +60 -0
  38. package/dist/cjs/interfaces/password.interface.js +2 -0
  39. package/dist/esm/core/crypto/cryptoManager.d.ts +111 -0
  40. package/dist/esm/core/crypto/cryptoManager.js +186 -0
  41. package/dist/esm/core/crypto/decrypt.js +7 -7
  42. package/dist/esm/core/crypto/encrypt.js +5 -5
  43. package/dist/esm/core/crypto/hmac.js +2 -2
  44. package/dist/esm/core/crypto/index.d.ts +5 -4
  45. package/dist/esm/core/crypto/index.js +5 -4
  46. package/dist/esm/core/crypto/random.js +3 -3
  47. package/dist/esm/core/jwt/decode.d.ts +1 -1
  48. package/dist/esm/core/jwt/decode.js +3 -3
  49. package/dist/esm/core/jwt/extractToken.js +7 -7
  50. package/dist/esm/core/jwt/generateTokens.d.ts +2 -2
  51. package/dist/esm/core/jwt/generateTokens.js +12 -8
  52. package/dist/esm/core/jwt/index.d.ts +8 -8
  53. package/dist/esm/core/jwt/index.js +8 -8
  54. package/dist/esm/core/jwt/jwtManager.d.ts +67 -0
  55. package/dist/esm/core/jwt/jwtManager.js +292 -0
  56. package/dist/esm/core/jwt/parseDuration.js +3 -3
  57. package/dist/esm/core/jwt/signToken.d.ts +1 -1
  58. package/dist/esm/core/jwt/signToken.js +9 -9
  59. package/dist/esm/core/jwt/types.d.ts +1 -1
  60. package/dist/esm/core/jwt/validateToken.d.ts +2 -2
  61. package/dist/esm/core/jwt/validateToken.js +3 -3
  62. package/dist/esm/core/jwt/verify.d.ts +3 -2
  63. package/dist/esm/core/jwt/verify.js +1 -1
  64. package/dist/esm/core/password/hash.js +3 -3
  65. package/dist/esm/core/password/index.d.ts +3 -3
  66. package/dist/esm/core/password/index.js +3 -3
  67. package/dist/esm/core/password/passwordManager.d.ts +29 -0
  68. package/dist/esm/core/password/passwordManager.js +236 -0
  69. package/dist/esm/core/password/strength.d.ts +1 -1
  70. package/dist/esm/core/password/strength.js +5 -5
  71. package/dist/esm/core/password/utils.js +4 -4
  72. package/dist/esm/core/password/verify.js +2 -2
  73. package/dist/esm/index.d.ts +9 -5
  74. package/dist/esm/index.js +7 -7
  75. package/dist/esm/interfaces/jwt.interface.d.ts +47 -0
  76. package/dist/esm/interfaces/jwt.interface.js +1 -0
  77. package/dist/esm/interfaces/password.interface.d.ts +60 -0
  78. package/dist/esm/interfaces/password.interface.js +1 -0
  79. package/dist/types/core/crypto/cryptoManager.d.ts +111 -0
  80. package/dist/types/core/crypto/index.d.ts +5 -4
  81. package/dist/types/core/jwt/decode.d.ts +1 -1
  82. package/dist/types/core/jwt/generateTokens.d.ts +2 -2
  83. package/dist/types/core/jwt/index.d.ts +8 -8
  84. package/dist/types/core/jwt/jwtManager.d.ts +67 -0
  85. package/dist/types/core/jwt/signToken.d.ts +1 -1
  86. package/dist/types/core/jwt/types.d.ts +1 -1
  87. package/dist/types/core/jwt/validateToken.d.ts +2 -2
  88. package/dist/types/core/jwt/verify.d.ts +3 -2
  89. package/dist/types/core/password/index.d.ts +3 -3
  90. package/dist/types/core/password/passwordManager.d.ts +29 -0
  91. package/dist/types/core/password/strength.d.ts +1 -1
  92. package/dist/types/index.d.ts +9 -5
  93. package/dist/types/interfaces/jwt.interface.d.ts +47 -0
  94. package/dist/types/interfaces/password.interface.d.ts +60 -0
  95. package/package.json +4 -3
@@ -0,0 +1,292 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import { signToken } from './signToken';
3
+ import { safeVerifyToken, verifyToken } from './verify';
4
+ import { BadRequestError, UnauthorizedError, ValidationError, } from '@naman_deep_singh/errors-utils';
5
+ import { LRUCache } from '@naman_deep_singh/js-extensions';
6
+ export class JWTManager {
7
+ constructor(config) {
8
+ this.accessSecret = config.accessSecret;
9
+ this.refreshSecret = config.refreshSecret;
10
+ this.accessExpiry = config.accessExpiry || '15m';
11
+ this.refreshExpiry = config.refreshExpiry || '7d';
12
+ this.cacheTTL = 5 * 60 * 1000; // 5 minutes default TTL
13
+ if (config.enableCaching) {
14
+ this.cache = new LRUCache(config.maxCacheSize || 100);
15
+ }
16
+ }
17
+ /**
18
+ * Generate both access and refresh tokens
19
+ */
20
+ async generateTokens(payload) {
21
+ try {
22
+ this.validatePayload(payload);
23
+ const accessToken = await this.generateAccessToken(payload);
24
+ const refreshToken = await this.generateRefreshToken(payload);
25
+ return {
26
+ accessToken,
27
+ refreshToken,
28
+ };
29
+ }
30
+ catch (error) {
31
+ if (error instanceof BadRequestError ||
32
+ error instanceof ValidationError) {
33
+ throw error;
34
+ }
35
+ throw new BadRequestError('Failed to generate tokens');
36
+ }
37
+ }
38
+ /**
39
+ * Generate access token
40
+ */
41
+ async generateAccessToken(payload) {
42
+ try {
43
+ this.validatePayload(payload);
44
+ const token = signToken(payload, this.accessSecret, this.accessExpiry, {
45
+ algorithm: 'HS256',
46
+ });
47
+ return token;
48
+ }
49
+ catch (error) {
50
+ if (error instanceof BadRequestError ||
51
+ error instanceof ValidationError) {
52
+ throw error;
53
+ }
54
+ throw new BadRequestError('Failed to generate access token');
55
+ }
56
+ }
57
+ /**
58
+ * Generate refresh token
59
+ */
60
+ async generateRefreshToken(payload) {
61
+ try {
62
+ this.validatePayload(payload);
63
+ const token = signToken(payload, this.refreshSecret, this.refreshExpiry, {
64
+ algorithm: 'HS256',
65
+ });
66
+ return token;
67
+ }
68
+ catch (error) {
69
+ if (error instanceof BadRequestError ||
70
+ error instanceof ValidationError) {
71
+ throw error;
72
+ }
73
+ throw new BadRequestError('Failed to generate refresh token');
74
+ }
75
+ }
76
+ /**
77
+ * Verify access token
78
+ */
79
+ async verifyAccessToken(token) {
80
+ try {
81
+ if (!token || typeof token !== 'string') {
82
+ throw new ValidationError('Access token must be a non-empty string');
83
+ }
84
+ const cacheKey = `access_${token}`;
85
+ if (this.cache) {
86
+ const cached = this.cache.get(cacheKey);
87
+ if (cached && Date.now() - cached.timestamp <= this.cacheTTL) {
88
+ if (!cached.valid) {
89
+ throw new UnauthorizedError('Access token is invalid or expired');
90
+ }
91
+ return cached.payload;
92
+ }
93
+ }
94
+ const decoded = verifyToken(token, this.accessSecret);
95
+ if (this.cache) {
96
+ this.cache.set(cacheKey, {
97
+ valid: true,
98
+ payload: decoded,
99
+ timestamp: Date.now(),
100
+ });
101
+ }
102
+ return decoded;
103
+ }
104
+ catch (error) {
105
+ if (error instanceof ValidationError ||
106
+ error instanceof UnauthorizedError) {
107
+ throw error;
108
+ }
109
+ if (error instanceof Error && error.name === 'TokenExpiredError') {
110
+ throw new UnauthorizedError('Access token has expired');
111
+ }
112
+ if (error instanceof Error && error.name === 'JsonWebTokenError') {
113
+ throw new UnauthorizedError('Access token is invalid');
114
+ }
115
+ throw new UnauthorizedError('Failed to verify access token');
116
+ }
117
+ }
118
+ /**
119
+ * Verify refresh token
120
+ */
121
+ async verifyRefreshToken(token) {
122
+ try {
123
+ if (!token || typeof token !== 'string') {
124
+ throw new ValidationError('Refresh token must be a non-empty string');
125
+ }
126
+ const cacheKey = `refresh_${token}`;
127
+ if (this.cache) {
128
+ const cached = this.cache.get(cacheKey);
129
+ if (cached) {
130
+ if (!cached.valid) {
131
+ throw new UnauthorizedError('Refresh token is invalid or expired');
132
+ }
133
+ return cached.payload;
134
+ }
135
+ }
136
+ const decoded = verifyToken(token, this.refreshSecret);
137
+ if (this.cache) {
138
+ this.cache.set(cacheKey, { valid: true, payload: decoded, timestamp: Date.now() });
139
+ }
140
+ return decoded;
141
+ }
142
+ catch (error) {
143
+ if (error instanceof ValidationError ||
144
+ error instanceof UnauthorizedError) {
145
+ throw error;
146
+ }
147
+ if (error instanceof Error && error.name === 'TokenExpiredError') {
148
+ throw new UnauthorizedError('Refresh token has expired');
149
+ }
150
+ if (error instanceof Error && error.name === 'JsonWebTokenError') {
151
+ throw new UnauthorizedError('Refresh token is invalid');
152
+ }
153
+ throw new UnauthorizedError('Failed to verify refresh token');
154
+ }
155
+ }
156
+ /**
157
+ * Decode token without verification
158
+ */
159
+ decodeToken(token, complete = false) {
160
+ try {
161
+ if (!token || typeof token !== 'string') {
162
+ throw new ValidationError('Token must be a non-empty string');
163
+ }
164
+ return jwt.decode(token, { complete });
165
+ }
166
+ catch (error) {
167
+ if (error instanceof ValidationError) {
168
+ throw error;
169
+ }
170
+ return null;
171
+ }
172
+ }
173
+ /**
174
+ * Extract token from Authorization header
175
+ */
176
+ extractTokenFromHeader(authHeader) {
177
+ try {
178
+ if (!authHeader || typeof authHeader !== 'string') {
179
+ return null;
180
+ }
181
+ const parts = authHeader.split(' ');
182
+ if (parts.length !== 2 || parts[0] !== 'Bearer') {
183
+ return null;
184
+ }
185
+ return parts[1];
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * Validate token without throwing exceptions
193
+ */
194
+ validateToken(token, secret, options = {}) {
195
+ try {
196
+ if (!token || typeof token !== 'string') {
197
+ return false;
198
+ }
199
+ const result = safeVerifyToken(token, secret);
200
+ return result.valid;
201
+ }
202
+ catch {
203
+ return false;
204
+ }
205
+ }
206
+ /**
207
+ * Rotate refresh token
208
+ */
209
+ async rotateRefreshToken(oldToken) {
210
+ try {
211
+ if (!oldToken || typeof oldToken !== 'string') {
212
+ throw new ValidationError('Old refresh token must be a non-empty string');
213
+ }
214
+ const decoded = await this.verifyRefreshToken(oldToken);
215
+ if (typeof decoded === 'string') {
216
+ throw new ValidationError('Invalid token payload — expected JWT payload object');
217
+ }
218
+ // Create new payload without issued/expired timestamps
219
+ const payload = { ...decoded };
220
+ delete payload.iat;
221
+ delete payload.exp;
222
+ // Generate new refresh token
223
+ const newToken = signToken(payload, this.refreshSecret, this.refreshExpiry);
224
+ return newToken;
225
+ }
226
+ catch (error) {
227
+ if (error instanceof ValidationError ||
228
+ error instanceof UnauthorizedError) {
229
+ throw error;
230
+ }
231
+ throw new BadRequestError('Failed to rotate refresh token');
232
+ }
233
+ }
234
+ /**
235
+ * Check if token is expired
236
+ */
237
+ isTokenExpired(token) {
238
+ try {
239
+ const decoded = this.decodeToken(token);
240
+ if (!decoded || !decoded.exp) {
241
+ return true;
242
+ }
243
+ const currentTime = Math.floor(Date.now() / 1000);
244
+ return decoded.exp < currentTime;
245
+ }
246
+ catch {
247
+ return true;
248
+ }
249
+ }
250
+ /**
251
+ * Get token expiration date
252
+ */
253
+ getTokenExpiration(token) {
254
+ try {
255
+ const decoded = this.decodeToken(token);
256
+ if (!decoded || !decoded.exp) {
257
+ return null;
258
+ }
259
+ return new Date(decoded.exp * 1000);
260
+ }
261
+ catch {
262
+ return null;
263
+ }
264
+ }
265
+ /**
266
+ * Clear token cache
267
+ */
268
+ clearCache() {
269
+ this.cache?.clear();
270
+ }
271
+ /**
272
+ * Get cache statistics
273
+ */
274
+ getCacheStats() {
275
+ if (!this.cache)
276
+ return null;
277
+ // Note: LRUCache doesn't expose internal size, so we return maxSize only
278
+ return {
279
+ size: -1, // Size not available from LRUCache
280
+ maxSize: this.cache.maxSize,
281
+ };
282
+ }
283
+ // Private helper methods
284
+ validatePayload(payload) {
285
+ if (!payload || typeof payload !== 'object') {
286
+ throw new ValidationError('Payload must be a non-null object');
287
+ }
288
+ if (Object.keys(payload).length === 0) {
289
+ throw new ValidationError('Payload cannot be empty');
290
+ }
291
+ }
292
+ }
@@ -3,16 +3,16 @@ const TIME_UNITS = {
3
3
  m: 60,
4
4
  h: 3600,
5
5
  d: 86400,
6
- w: 604800
6
+ w: 604800,
7
7
  };
8
8
  export function parseDuration(input) {
9
- if (typeof input === "number")
9
+ if (typeof input === 'number')
10
10
  return input;
11
11
  const regex = /(\d+)\s*(s|m|h|d|w)/gi;
12
12
  let totalSeconds = 0;
13
13
  let match;
14
14
  while ((match = regex.exec(input)) !== null) {
15
- const value = parseInt(match[1], 10);
15
+ const value = Number.parseInt(match[1], 10);
16
16
  const unit = match[2].toLowerCase();
17
17
  if (!TIME_UNITS[unit]) {
18
18
  throw new Error(`Invalid time unit: ${unit}`);
@@ -1,2 +1,2 @@
1
- import { Secret, SignOptions } from "jsonwebtoken";
1
+ import { type Secret, type SignOptions } from 'jsonwebtoken';
2
2
  export declare const signToken: (payload: Record<string, unknown>, secret: Secret, expiresIn?: string | number, options?: SignOptions) => string;
@@ -1,22 +1,22 @@
1
- import { sign } from "jsonwebtoken";
2
- import { parseDuration } from "./parseDuration";
1
+ import { sign } from 'jsonwebtoken';
2
+ import { parseDuration } from './parseDuration';
3
3
  function getExpiryTimestamp(seconds) {
4
4
  return Math.floor(Date.now() / 1000) + seconds;
5
5
  }
6
- export const signToken = (payload, secret, expiresIn = "1h", options = {}) => {
6
+ export const signToken = (payload, secret, expiresIn = '1h', options = {}) => {
7
7
  const seconds = parseDuration(expiresIn);
8
8
  if (!seconds || seconds < 10) {
9
- throw new Error("Token expiry too small");
9
+ throw new Error('Token expiry too small');
10
10
  }
11
11
  const tokenPayload = {
12
- ...payload
12
+ ...payload,
13
13
  };
14
- if (!("exp" in payload))
14
+ if (!('exp' in payload))
15
15
  tokenPayload.exp = getExpiryTimestamp(seconds);
16
- if (!("iat" in payload))
16
+ if (!('iat' in payload))
17
17
  tokenPayload.iat = Math.floor(Date.now() / 1000);
18
18
  return sign(tokenPayload, secret, {
19
- algorithm: "HS256",
20
- ...options
19
+ algorithm: 'HS256',
20
+ ...options,
21
21
  });
22
22
  };
@@ -1,4 +1,4 @@
1
- import { JwtPayload } from "jsonwebtoken";
1
+ import type { JwtPayload } from 'jsonwebtoken';
2
2
  export interface AccessTokenBrand {
3
3
  readonly access: unique symbol;
4
4
  }
@@ -1,8 +1,8 @@
1
- import { JwtPayload } from "node_modules/@types/jsonwebtoken";
1
+ import type { JwtPayload } from 'node_modules/@types/jsonwebtoken';
2
2
  export interface TokenRequirements {
3
3
  requiredFields?: string[];
4
4
  forbiddenFields?: string[];
5
- validateTypes?: Record<string, "string" | "number" | "boolean">;
5
+ validateTypes?: Record<string, 'string' | 'number' | 'boolean'>;
6
6
  }
7
7
  export declare function validateTokenPayload(payload: Record<string, unknown>, rules?: TokenRequirements): {
8
8
  valid: true;
@@ -1,7 +1,7 @@
1
1
  export function validateTokenPayload(payload, rules = {
2
- requiredFields: ["exp", "iat"]
2
+ requiredFields: ['exp', 'iat'],
3
3
  }) {
4
- const { requiredFields = [], forbiddenFields = [], validateTypes = {} } = rules;
4
+ const { requiredFields = [], forbiddenFields = [], validateTypes = {}, } = rules;
5
5
  // 1. Required fields
6
6
  for (const field of requiredFields) {
7
7
  if (!(field in payload)) {
@@ -20,7 +20,7 @@ export function validateTokenPayload(payload, rules = {
20
20
  if (key in payload && typeof payload[key] !== expectedType) {
21
21
  return {
22
22
  valid: false,
23
- error: `Invalid type for ${key}. Expected ${expectedType}.`
23
+ error: `Invalid type for ${key}. Expected ${expectedType}.`,
24
24
  };
25
25
  }
26
26
  }
@@ -1,5 +1,6 @@
1
- import jwt, { Secret, JwtPayload } from "jsonwebtoken";
2
- import { VerificationResult } from "./types";
1
+ import type jwt from 'jsonwebtoken';
2
+ import { type JwtPayload, type Secret } from 'jsonwebtoken';
3
+ import type { VerificationResult } from './types';
3
4
  /**
4
5
  * Verify token (throws if invalid or expired)
5
6
  */
@@ -1,4 +1,4 @@
1
- import { verify } from "jsonwebtoken";
1
+ import { verify } from 'jsonwebtoken';
2
2
  /**
3
3
  * Verify token (throws if invalid or expired)
4
4
  */
@@ -1,6 +1,6 @@
1
- import bcrypt from "bcryptjs";
2
- import { ensureValidPassword } from "./utils";
3
- import { InternalServerError } from "@naman_deep_singh/errors-utils";
1
+ import { InternalServerError } from '@naman_deep_singh/errors-utils';
2
+ import bcrypt from 'bcryptjs';
3
+ import { ensureValidPassword } from './utils';
4
4
  /**
5
5
  * Hash a password asynchronously using bcrypt.
6
6
  */
@@ -1,3 +1,3 @@
1
- export * from "./hash";
2
- export * from "./strength";
3
- export * from "./verify";
1
+ export * from './hash';
2
+ export * from './strength';
3
+ export * from './verify';
@@ -1,3 +1,3 @@
1
- export * from "./hash";
2
- export * from "./strength";
3
- export * from "./verify";
1
+ export * from './hash';
2
+ export * from './strength';
3
+ export * from './verify';
@@ -0,0 +1,29 @@
1
+ import type { HashedPassword, IPasswordManager, PasswordConfig, PasswordStrength, PasswordValidationResult } from '../../interfaces/password.interface';
2
+ export declare class PasswordManager implements IPasswordManager {
3
+ private defaultConfig;
4
+ constructor(config?: PasswordConfig);
5
+ /**
6
+ * Hash a password asynchronously using bcrypt
7
+ */
8
+ hash(password: string, salt?: string): Promise<HashedPassword>;
9
+ /**
10
+ * Verify password against hash and salt
11
+ */
12
+ verify(password: string, hash: string, salt: string): Promise<boolean>;
13
+ /**
14
+ * Generate a random password
15
+ */
16
+ generate(length?: number, options?: PasswordConfig): string;
17
+ /**
18
+ * Validate password against configuration
19
+ */
20
+ validate(password: string, config?: PasswordConfig): PasswordValidationResult;
21
+ /**
22
+ * Check password strength
23
+ */
24
+ checkStrength(password: string): PasswordStrength;
25
+ /**
26
+ * Check if password hash needs upgrade (different salt rounds)
27
+ */
28
+ needsUpgrade(hash: string, currentConfig: PasswordConfig): boolean;
29
+ }