@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,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,6 +1,6 @@
|
|
|
1
1
|
import jwt from 'jsonwebtoken';
|
|
2
2
|
import { signToken } from './signToken';
|
|
3
|
-
import { safeVerifyToken
|
|
3
|
+
import { safeVerifyToken } from './verify';
|
|
4
4
|
import { BadRequestError, UnauthorizedError, ValidationError, } from '@naman_deep_singh/errors-utils';
|
|
5
5
|
import { LRUCache } from '@naman_deep_singh/js-extensions';
|
|
6
6
|
export class JWTManager {
|
|
@@ -9,284 +9,154 @@ export class JWTManager {
|
|
|
9
9
|
this.refreshSecret = config.refreshSecret;
|
|
10
10
|
this.accessExpiry = config.accessExpiry || '15m';
|
|
11
11
|
this.refreshExpiry = config.refreshExpiry || '7d';
|
|
12
|
-
this.cacheTTL = 5 * 60 * 1000; // 5 minutes
|
|
12
|
+
this.cacheTTL = 5 * 60 * 1000; // 5 minutes
|
|
13
13
|
if (config.enableCaching) {
|
|
14
14
|
this.cache = new LRUCache(config.maxCacheSize || 100);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Generate both access and refresh tokens
|
|
19
|
-
*/
|
|
17
|
+
/** Generate both access and refresh tokens */
|
|
20
18
|
async generateTokens(payload) {
|
|
21
19
|
try {
|
|
22
20
|
this.validatePayload(payload);
|
|
23
21
|
const accessToken = await this.generateAccessToken(payload);
|
|
24
22
|
const refreshToken = await this.generateRefreshToken(payload);
|
|
25
|
-
return {
|
|
26
|
-
accessToken,
|
|
27
|
-
refreshToken,
|
|
28
|
-
};
|
|
23
|
+
return { accessToken, refreshToken };
|
|
29
24
|
}
|
|
30
25
|
catch (error) {
|
|
31
|
-
if (error instanceof BadRequestError ||
|
|
32
|
-
error instanceof ValidationError) {
|
|
26
|
+
if (error instanceof BadRequestError || error instanceof ValidationError)
|
|
33
27
|
throw error;
|
|
34
|
-
}
|
|
35
|
-
throw new BadRequestError('Failed to generate tokens');
|
|
28
|
+
throw new BadRequestError({ message: 'Failed to generate tokens' }, error instanceof Error ? error : undefined);
|
|
36
29
|
}
|
|
37
30
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Generate access token
|
|
40
|
-
*/
|
|
31
|
+
/** Generate access token */
|
|
41
32
|
async generateAccessToken(payload) {
|
|
42
33
|
try {
|
|
43
34
|
this.validatePayload(payload);
|
|
44
|
-
const token = signToken(payload, this.accessSecret, this.accessExpiry, {
|
|
45
|
-
algorithm: 'HS256',
|
|
46
|
-
});
|
|
35
|
+
const token = signToken(payload, this.accessSecret, this.accessExpiry, { algorithm: 'HS256' });
|
|
47
36
|
return token;
|
|
48
37
|
}
|
|
49
38
|
catch (error) {
|
|
50
|
-
if (error instanceof BadRequestError ||
|
|
51
|
-
error instanceof ValidationError) {
|
|
39
|
+
if (error instanceof BadRequestError || error instanceof ValidationError)
|
|
52
40
|
throw error;
|
|
53
|
-
}
|
|
54
|
-
throw new BadRequestError('Failed to generate access token');
|
|
41
|
+
throw new BadRequestError({ message: 'Failed to generate access token' }, error instanceof Error ? error : undefined);
|
|
55
42
|
}
|
|
56
43
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Generate refresh token
|
|
59
|
-
*/
|
|
44
|
+
/** Generate refresh token */
|
|
60
45
|
async generateRefreshToken(payload) {
|
|
61
46
|
try {
|
|
62
47
|
this.validatePayload(payload);
|
|
63
|
-
const token = signToken(payload, this.refreshSecret, this.refreshExpiry, {
|
|
64
|
-
algorithm: 'HS256',
|
|
65
|
-
});
|
|
48
|
+
const token = signToken(payload, this.refreshSecret, this.refreshExpiry, { algorithm: 'HS256' });
|
|
66
49
|
return token;
|
|
67
50
|
}
|
|
68
51
|
catch (error) {
|
|
69
|
-
if (error instanceof BadRequestError ||
|
|
70
|
-
error instanceof ValidationError) {
|
|
52
|
+
if (error instanceof BadRequestError || error instanceof ValidationError)
|
|
71
53
|
throw error;
|
|
72
|
-
}
|
|
73
|
-
throw new BadRequestError('Failed to generate refresh token');
|
|
54
|
+
throw new BadRequestError({ message: 'Failed to generate refresh token' }, error instanceof Error ? error : undefined);
|
|
74
55
|
}
|
|
75
56
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Verify access token
|
|
78
|
-
*/
|
|
57
|
+
/** Verify access token */
|
|
79
58
|
async verifyAccessToken(token) {
|
|
80
|
-
|
|
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
|
-
}
|
|
59
|
+
return this.verifyTokenWithCache(token, this.accessSecret, 'access');
|
|
117
60
|
}
|
|
118
|
-
/**
|
|
119
|
-
* Verify refresh token
|
|
120
|
-
*/
|
|
61
|
+
/** Verify refresh token */
|
|
121
62
|
async verifyRefreshToken(token) {
|
|
122
|
-
|
|
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
|
-
}
|
|
63
|
+
return this.verifyTokenWithCache(token, this.refreshSecret, 'refresh');
|
|
155
64
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Decode token without verification
|
|
158
|
-
*/
|
|
65
|
+
/** Decode token without verification */
|
|
159
66
|
decodeToken(token, complete = false) {
|
|
160
|
-
|
|
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
|
-
}
|
|
67
|
+
if (!token || typeof token !== 'string')
|
|
170
68
|
return null;
|
|
171
|
-
}
|
|
69
|
+
return jwt.decode(token, { complete });
|
|
172
70
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Extract token from Authorization header
|
|
175
|
-
*/
|
|
71
|
+
/** Extract token from Authorization header */
|
|
176
72
|
extractTokenFromHeader(authHeader) {
|
|
177
|
-
|
|
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 {
|
|
73
|
+
if (!authHeader || typeof authHeader !== 'string')
|
|
188
74
|
return null;
|
|
189
|
-
|
|
75
|
+
const parts = authHeader.split(' ');
|
|
76
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer')
|
|
77
|
+
return null;
|
|
78
|
+
return parts[1];
|
|
190
79
|
}
|
|
191
|
-
/**
|
|
192
|
-
|
|
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 {
|
|
80
|
+
/** Validate token without throwing exceptions */
|
|
81
|
+
validateToken(token, secret) {
|
|
82
|
+
if (!token || typeof token !== 'string')
|
|
203
83
|
return false;
|
|
204
|
-
|
|
84
|
+
return safeVerifyToken(token, secret).valid;
|
|
205
85
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Rotate refresh token
|
|
208
|
-
*/
|
|
86
|
+
/** Rotate refresh token */
|
|
209
87
|
async rotateRefreshToken(oldToken) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
}
|
|
88
|
+
if (!oldToken || typeof oldToken !== 'string') {
|
|
89
|
+
throw new ValidationError({ message: 'Old refresh token must be a non-empty string' });
|
|
90
|
+
}
|
|
91
|
+
const decoded = await this.verifyRefreshToken(oldToken);
|
|
92
|
+
const payload = { ...decoded };
|
|
93
|
+
delete payload.iat;
|
|
94
|
+
delete payload.exp;
|
|
95
|
+
const newToken = signToken(payload, this.refreshSecret, this.refreshExpiry);
|
|
96
|
+
return newToken;
|
|
233
97
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Check if token is expired
|
|
236
|
-
*/
|
|
98
|
+
/** Check if token is expired */
|
|
237
99
|
isTokenExpired(token) {
|
|
238
100
|
try {
|
|
239
101
|
const decoded = this.decodeToken(token);
|
|
240
|
-
if (!decoded || !decoded.exp)
|
|
102
|
+
if (!decoded || !decoded.exp)
|
|
241
103
|
return true;
|
|
242
|
-
|
|
243
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
244
|
-
return decoded.exp < currentTime;
|
|
104
|
+
return decoded.exp < Math.floor(Date.now() / 1000);
|
|
245
105
|
}
|
|
246
106
|
catch {
|
|
247
107
|
return true;
|
|
248
108
|
}
|
|
249
109
|
}
|
|
250
|
-
/**
|
|
251
|
-
* Get token expiration date
|
|
252
|
-
*/
|
|
110
|
+
/** Get token expiration date */
|
|
253
111
|
getTokenExpiration(token) {
|
|
254
112
|
try {
|
|
255
113
|
const decoded = this.decodeToken(token);
|
|
256
|
-
if (!decoded || !decoded.exp)
|
|
114
|
+
if (!decoded || !decoded.exp)
|
|
257
115
|
return null;
|
|
258
|
-
}
|
|
259
116
|
return new Date(decoded.exp * 1000);
|
|
260
117
|
}
|
|
261
118
|
catch {
|
|
262
119
|
return null;
|
|
263
120
|
}
|
|
264
121
|
}
|
|
265
|
-
/**
|
|
266
|
-
* Clear token cache
|
|
267
|
-
*/
|
|
122
|
+
/** Clear token cache */
|
|
268
123
|
clearCache() {
|
|
269
124
|
this.cache?.clear();
|
|
270
125
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Get cache statistics
|
|
273
|
-
*/
|
|
126
|
+
/** Get cache statistics */
|
|
274
127
|
getCacheStats() {
|
|
275
128
|
if (!this.cache)
|
|
276
129
|
return null;
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
size: -1, // Size not available from LRUCache
|
|
280
|
-
maxSize: this.cache.maxSize,
|
|
281
|
-
};
|
|
130
|
+
return { size: -1, maxSize: this.cache.maxSize };
|
|
282
131
|
}
|
|
283
|
-
|
|
132
|
+
/** Private helper methods */
|
|
284
133
|
validatePayload(payload) {
|
|
285
134
|
if (!payload || typeof payload !== 'object') {
|
|
286
|
-
throw new ValidationError('Payload must be a non-null object');
|
|
135
|
+
throw new ValidationError({ message: 'Payload must be a non-null object' });
|
|
287
136
|
}
|
|
288
137
|
if (Object.keys(payload).length === 0) {
|
|
289
|
-
throw new ValidationError('Payload cannot be empty');
|
|
138
|
+
throw new ValidationError({ message: 'Payload cannot be empty' });
|
|
290
139
|
}
|
|
291
140
|
}
|
|
141
|
+
async verifyTokenWithCache(token, secret, type) {
|
|
142
|
+
if (!token || typeof token !== 'string') {
|
|
143
|
+
throw new ValidationError({ message: `${type} token must be a non-empty string` });
|
|
144
|
+
}
|
|
145
|
+
const cacheKey = `${type}_${token}`;
|
|
146
|
+
if (this.cache) {
|
|
147
|
+
const cached = this.cache.get(cacheKey);
|
|
148
|
+
if (cached && Date.now() - cached.timestamp <= this.cacheTTL) {
|
|
149
|
+
if (!cached.valid)
|
|
150
|
+
throw new UnauthorizedError({ message: `${type} token is invalid or expired` });
|
|
151
|
+
return cached.payload;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const { valid, payload, error } = safeVerifyToken(token, secret);
|
|
155
|
+
if (!valid || !payload || typeof payload === 'string') {
|
|
156
|
+
this.cache?.set(cacheKey, { valid: false, payload: {}, timestamp: Date.now() });
|
|
157
|
+
throw new UnauthorizedError({ message: `${type} token is invalid or expired`, cause: error });
|
|
158
|
+
}
|
|
159
|
+
this.cache?.set(cacheKey, { valid: true, payload, timestamp: Date.now() });
|
|
160
|
+
return payload;
|
|
161
|
+
}
|
|
292
162
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "@naman_deep_singh/errors-utils";
|
|
1
2
|
const TIME_UNITS = {
|
|
2
3
|
s: 1,
|
|
3
4
|
m: 60,
|
|
@@ -15,12 +16,12 @@ export function parseDuration(input) {
|
|
|
15
16
|
const value = Number.parseInt(match[1], 10);
|
|
16
17
|
const unit = match[2].toLowerCase();
|
|
17
18
|
if (!TIME_UNITS[unit]) {
|
|
18
|
-
throw new
|
|
19
|
+
throw new ValidationError({ message: `Invalid time unit: ${unit}` });
|
|
19
20
|
}
|
|
20
21
|
totalSeconds += value * TIME_UNITS[unit];
|
|
21
22
|
}
|
|
22
23
|
if (totalSeconds === 0) {
|
|
23
|
-
throw new
|
|
24
|
+
throw new ValidationError({ message: `Invalid expiry format: "${input}"` });
|
|
24
25
|
}
|
|
25
26
|
return totalSeconds;
|
|
26
27
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { sign } from 'jsonwebtoken';
|
|
2
2
|
import { parseDuration } from './parseDuration';
|
|
3
|
+
import { ValidationError } from '@naman_deep_singh/errors-utils';
|
|
3
4
|
function getExpiryTimestamp(seconds) {
|
|
4
5
|
return Math.floor(Date.now() / 1000) + seconds;
|
|
5
6
|
}
|
|
6
7
|
export const signToken = (payload, secret, expiresIn = '1h', options = {}) => {
|
|
7
8
|
const seconds = parseDuration(expiresIn);
|
|
8
9
|
if (!seconds || seconds < 10) {
|
|
9
|
-
throw new
|
|
10
|
+
throw new ValidationError({ message: 'Token expiry too small' });
|
|
10
11
|
}
|
|
11
12
|
const tokenPayload = {
|
|
12
13
|
...payload,
|
|
@@ -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;
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { ValidationError } from '@naman_deep_singh/errors-utils';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a JWT payload according to the provided rules.
|
|
4
|
+
* Throws ValidationError if validation fails.
|
|
5
|
+
*/
|
|
6
|
+
export function validateTokenPayload(payload, rules = { requiredFields: ['exp', 'iat'] }) {
|
|
7
|
+
const { requiredFields = [], forbiddenFields = [], validateTypes = {} } = rules;
|
|
5
8
|
// 1. Required fields
|
|
6
9
|
for (const field of requiredFields) {
|
|
7
10
|
if (!(field in payload)) {
|
|
8
|
-
|
|
11
|
+
throw new ValidationError(`Missing required field: ${field}`);
|
|
9
12
|
}
|
|
10
13
|
}
|
|
11
14
|
// 2. Forbidden fields
|
|
12
15
|
for (const field of forbiddenFields) {
|
|
13
16
|
if (field in payload) {
|
|
14
|
-
|
|
17
|
+
throw new ValidationError(`Forbidden field in token: ${field}`);
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
20
|
// 3. Type validation
|
|
18
21
|
for (const key in validateTypes) {
|
|
19
22
|
const expectedType = validateTypes[key];
|
|
20
23
|
if (key in payload && typeof payload[key] !== expectedType) {
|
|
21
|
-
|
|
22
|
-
valid: false,
|
|
23
|
-
error: `Invalid type for ${key}. Expected ${expectedType}.`,
|
|
24
|
-
};
|
|
24
|
+
throw new ValidationError(`Invalid type for ${key}. Expected ${expectedType}, got ${typeof payload[key]}`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
return { valid: true };
|
|
28
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a JWT payload is expired.
|
|
30
|
+
* Returns true if expired or missing 'exp'.
|
|
31
|
+
*/
|
|
29
32
|
export function isTokenExpired(payload) {
|
|
30
33
|
if (!payload.exp)
|
|
31
34
|
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;
|