@veloxts/auth 0.3.3 → 0.3.5
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 +1157 -30
- package/dist/__integration__/fixtures.d.ts +41 -0
- package/dist/__integration__/fixtures.d.ts.map +1 -0
- package/dist/__integration__/fixtures.js +79 -0
- package/dist/__integration__/fixtures.js.map +1 -0
- package/dist/__integration__/setup.d.ts +26 -0
- package/dist/__integration__/setup.d.ts.map +1 -0
- package/dist/__integration__/setup.js +28 -0
- package/dist/__integration__/setup.js.map +1 -0
- package/dist/adapter.d.ts +710 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +581 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/better-auth.d.ts +271 -0
- package/dist/adapters/better-auth.d.ts.map +1 -0
- package/dist/adapters/better-auth.js +341 -0
- package/dist/adapters/better-auth.js.map +1 -0
- package/dist/adapters/index.d.ts +28 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/csrf.d.ts +300 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/csrf.js +402 -0
- package/dist/csrf.js.map +1 -0
- package/dist/guards.d.ts +142 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +259 -0
- package/dist/guards.js.map +1 -0
- package/dist/hash.d.ts +91 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +236 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +27 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -36
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts +157 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +489 -0
- package/dist/jwt.js.map +1 -0
- package/dist/middleware.d.ts +99 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +253 -0
- package/dist/middleware.js.map +1 -0
- package/dist/plugin.d.ts +125 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +193 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policies.d.ts +137 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +240 -0
- package/dist/policies.js.map +1 -0
- package/dist/rate-limit.d.ts +231 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +352 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/session.d.ts +500 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +801 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +261 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -7
package/dist/jwt.js
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT token utilities for @veloxts/auth
|
|
3
|
+
* @module auth/jwt
|
|
4
|
+
*/
|
|
5
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
6
|
+
import { AuthError } from './types.js';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const DEFAULT_ACCESS_EXPIRY = '15m';
|
|
11
|
+
const DEFAULT_REFRESH_EXPIRY = '7d';
|
|
12
|
+
/**
|
|
13
|
+
* Minimum JWT secret length (64 characters = 512 bits)
|
|
14
|
+
* HS256 requires at least 256 bits, but we require 512 for extra security margin
|
|
15
|
+
*/
|
|
16
|
+
const MIN_SECRET_LENGTH = 64;
|
|
17
|
+
/**
|
|
18
|
+
* Minimum unique characters in secret for entropy validation
|
|
19
|
+
*/
|
|
20
|
+
const MIN_SECRET_ENTROPY_CHARS = 16;
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Token Expiration Bounds (Security Phase 3.1)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Minimum access token expiry: 1 minute
|
|
26
|
+
* Shorter tokens increase security but may impact UX
|
|
27
|
+
*/
|
|
28
|
+
const MIN_ACCESS_TOKEN_SECONDS = 60;
|
|
29
|
+
/**
|
|
30
|
+
* Maximum access token expiry: 1 hour
|
|
31
|
+
* Longer lived tokens are a security risk if stolen
|
|
32
|
+
*/
|
|
33
|
+
const MAX_ACCESS_TOKEN_SECONDS = 60 * 60;
|
|
34
|
+
/**
|
|
35
|
+
* Minimum refresh token expiry: 1 hour
|
|
36
|
+
* Too short reduces usability
|
|
37
|
+
*/
|
|
38
|
+
const MIN_REFRESH_TOKEN_SECONDS = 60 * 60;
|
|
39
|
+
/**
|
|
40
|
+
* Maximum refresh token expiry: 30 days
|
|
41
|
+
* Longer lived refresh tokens increase risk window
|
|
42
|
+
*/
|
|
43
|
+
const MAX_REFRESH_TOKEN_SECONDS = 30 * 24 * 60 * 60;
|
|
44
|
+
/**
|
|
45
|
+
* Recommended maximum access token expiry: 15 minutes
|
|
46
|
+
* Beyond this, consider shorter lived tokens with refresh
|
|
47
|
+
*/
|
|
48
|
+
const RECOMMENDED_MAX_ACCESS_SECONDS = 15 * 60;
|
|
49
|
+
/**
|
|
50
|
+
* Recommended maximum refresh token expiry: 7 days
|
|
51
|
+
*/
|
|
52
|
+
const RECOMMENDED_MAX_REFRESH_SECONDS = 7 * 24 * 60 * 60;
|
|
53
|
+
/**
|
|
54
|
+
* Reserved JWT claims that cannot be overridden via additionalClaims
|
|
55
|
+
*/
|
|
56
|
+
const RESERVED_JWT_CLAIMS = new Set([
|
|
57
|
+
'sub',
|
|
58
|
+
'iss',
|
|
59
|
+
'aud',
|
|
60
|
+
'exp',
|
|
61
|
+
'iat',
|
|
62
|
+
'jti',
|
|
63
|
+
'nbf',
|
|
64
|
+
'type',
|
|
65
|
+
'email',
|
|
66
|
+
]);
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// JWT Implementation
|
|
69
|
+
// ============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Validates a time string format
|
|
72
|
+
* Supports: '1s', '15m', '1h', '7d', etc.
|
|
73
|
+
* Minimum valid value is '1s' (1 second)
|
|
74
|
+
*
|
|
75
|
+
* @returns true if valid, false otherwise
|
|
76
|
+
*/
|
|
77
|
+
export function isValidTimespan(time) {
|
|
78
|
+
const match = time.match(/^(\d+)([smhd])$/);
|
|
79
|
+
if (!match) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const value = parseInt(match[1], 10);
|
|
83
|
+
// Value must be at least 1
|
|
84
|
+
return value >= 1;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parses time string to seconds
|
|
88
|
+
* Supports: '15m', '1h', '7d', '30d', etc.
|
|
89
|
+
*/
|
|
90
|
+
export function parseTimeToSeconds(time) {
|
|
91
|
+
const match = time.match(/^(\d+)([smhd])$/);
|
|
92
|
+
if (!match) {
|
|
93
|
+
throw new AuthError(`Invalid time format: ${time}. Use format like '15m', '1h', '7d'`, 400, 'INVALID_TIME_FORMAT');
|
|
94
|
+
}
|
|
95
|
+
const value = parseInt(match[1], 10);
|
|
96
|
+
const unit = match[2];
|
|
97
|
+
switch (unit) {
|
|
98
|
+
case 's':
|
|
99
|
+
return value;
|
|
100
|
+
case 'm':
|
|
101
|
+
return value * 60;
|
|
102
|
+
case 'h':
|
|
103
|
+
return value * 60 * 60;
|
|
104
|
+
case 'd':
|
|
105
|
+
return value * 60 * 60 * 24;
|
|
106
|
+
default:
|
|
107
|
+
throw new AuthError(`Unknown time unit: ${unit}`, 400, 'INVALID_TIME_UNIT');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Base64url encode
|
|
112
|
+
*/
|
|
113
|
+
function base64urlEncode(data) {
|
|
114
|
+
const base64 = Buffer.from(data).toString('base64');
|
|
115
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Base64url decode
|
|
119
|
+
*/
|
|
120
|
+
function base64urlDecode(data) {
|
|
121
|
+
const base64 = data.replace(/-/g, '+').replace(/_/g, '/');
|
|
122
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
123
|
+
return Buffer.from(padded, 'base64').toString('utf8');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create HMAC-SHA256 signature
|
|
127
|
+
*/
|
|
128
|
+
function createSignature(data, secret) {
|
|
129
|
+
const hmac = createHmac('sha256', secret);
|
|
130
|
+
hmac.update(data);
|
|
131
|
+
return base64urlEncode(hmac.digest());
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Generate a unique token ID
|
|
135
|
+
*/
|
|
136
|
+
export function generateTokenId() {
|
|
137
|
+
return randomBytes(16).toString('hex');
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Validates token expiration against security bounds
|
|
141
|
+
*
|
|
142
|
+
* @param accessExpiry - Access token expiry string (e.g., '15m')
|
|
143
|
+
* @param refreshExpiry - Refresh token expiry string (e.g., '7d')
|
|
144
|
+
* @throws Error if expiration times are outside security bounds
|
|
145
|
+
*/
|
|
146
|
+
export function validateTokenExpiration(accessExpiry, refreshExpiry) {
|
|
147
|
+
const accessSeconds = parseTimeToSeconds(accessExpiry);
|
|
148
|
+
const refreshSeconds = parseTimeToSeconds(refreshExpiry);
|
|
149
|
+
// Validate access token bounds
|
|
150
|
+
if (accessSeconds < MIN_ACCESS_TOKEN_SECONDS) {
|
|
151
|
+
throw new AuthError(`Access token expiry (${accessExpiry} = ${accessSeconds}s) is below minimum of ` +
|
|
152
|
+
`${MIN_ACCESS_TOKEN_SECONDS}s (1 minute). Very short tokens cause excessive refreshes.`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
153
|
+
}
|
|
154
|
+
if (accessSeconds > MAX_ACCESS_TOKEN_SECONDS) {
|
|
155
|
+
throw new AuthError(`Access token expiry (${accessExpiry} = ${accessSeconds}s) exceeds maximum of ` +
|
|
156
|
+
`${MAX_ACCESS_TOKEN_SECONDS}s (1 hour). Long-lived access tokens are a security risk.`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
157
|
+
}
|
|
158
|
+
// Validate refresh token bounds
|
|
159
|
+
if (refreshSeconds < MIN_REFRESH_TOKEN_SECONDS) {
|
|
160
|
+
throw new AuthError(`Refresh token expiry (${refreshExpiry} = ${refreshSeconds}s) is below minimum of ` +
|
|
161
|
+
`${MIN_REFRESH_TOKEN_SECONDS}s (1 hour). Very short refresh tokens impact usability.`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
162
|
+
}
|
|
163
|
+
if (refreshSeconds > MAX_REFRESH_TOKEN_SECONDS) {
|
|
164
|
+
throw new AuthError(`Refresh token expiry (${refreshExpiry} = ${refreshSeconds}s) exceeds maximum of ` +
|
|
165
|
+
`${MAX_REFRESH_TOKEN_SECONDS}s (30 days). Long-lived refresh tokens increase attack window.`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
166
|
+
}
|
|
167
|
+
// Warn about exceeding recommended limits (non-fatal)
|
|
168
|
+
if (accessSeconds > RECOMMENDED_MAX_ACCESS_SECONDS) {
|
|
169
|
+
console.warn(`[Security] Access token expiry (${accessExpiry}) exceeds recommended maximum of 15 minutes. ` +
|
|
170
|
+
'Consider using shorter-lived access tokens with refresh.');
|
|
171
|
+
}
|
|
172
|
+
if (refreshSeconds > RECOMMENDED_MAX_REFRESH_SECONDS) {
|
|
173
|
+
console.warn(`[Security] Refresh token expiry (${refreshExpiry}) exceeds recommended maximum of 7 days. ` +
|
|
174
|
+
'Long-lived refresh tokens increase the window for token theft attacks.');
|
|
175
|
+
}
|
|
176
|
+
// Ensure refresh tokens outlive access tokens
|
|
177
|
+
if (refreshSeconds <= accessSeconds) {
|
|
178
|
+
throw new AuthError(`Refresh token expiry (${refreshExpiry} = ${refreshSeconds}s) must be longer than ` +
|
|
179
|
+
`access token expiry (${accessExpiry} = ${accessSeconds}s).`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// JWT Manager Class
|
|
184
|
+
// ============================================================================
|
|
185
|
+
/**
|
|
186
|
+
* JWT token manager
|
|
187
|
+
*
|
|
188
|
+
* Handles token creation, verification, and refresh.
|
|
189
|
+
* Uses HS256 (HMAC-SHA256) algorithm.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const jwt = new JwtManager({
|
|
194
|
+
* secret: process.env.JWT_SECRET!,
|
|
195
|
+
* accessTokenExpiry: '15m',
|
|
196
|
+
* refreshTokenExpiry: '7d',
|
|
197
|
+
* });
|
|
198
|
+
*
|
|
199
|
+
* // Create tokens for user
|
|
200
|
+
* const tokens = jwt.createTokenPair(user);
|
|
201
|
+
*
|
|
202
|
+
* // Verify access token
|
|
203
|
+
* const payload = jwt.verifyToken(tokens.accessToken);
|
|
204
|
+
*
|
|
205
|
+
* // Refresh tokens
|
|
206
|
+
* const newTokens = jwt.refreshTokens(tokens.refreshToken);
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export class JwtManager {
|
|
210
|
+
config;
|
|
211
|
+
constructor(config) {
|
|
212
|
+
// Validate secret length (Critical Fix #1)
|
|
213
|
+
if (!config.secret || config.secret.length < MIN_SECRET_LENGTH) {
|
|
214
|
+
throw new AuthError(`JWT secret must be at least ${MIN_SECRET_LENGTH} characters long (512 bits). ` +
|
|
215
|
+
'Generate with: openssl rand -base64 64', 500, 'INVALID_JWT_SECRET');
|
|
216
|
+
}
|
|
217
|
+
// Validate secret entropy - check for sufficient unique characters
|
|
218
|
+
const uniqueChars = new Set(config.secret).size;
|
|
219
|
+
if (uniqueChars < MIN_SECRET_ENTROPY_CHARS) {
|
|
220
|
+
throw new AuthError(`JWT secret has insufficient entropy (only ${uniqueChars} unique characters). ` +
|
|
221
|
+
'Use cryptographically random data with at least 16 unique characters.', 500, 'INVALID_JWT_SECRET');
|
|
222
|
+
}
|
|
223
|
+
// Validate accessTokenExpiry format if provided
|
|
224
|
+
if (config.accessTokenExpiry !== undefined && !isValidTimespan(config.accessTokenExpiry)) {
|
|
225
|
+
throw new AuthError(`Invalid accessTokenExpiry "${config.accessTokenExpiry}". ` +
|
|
226
|
+
`Use formats like "15m", "1h", "7d". Minimum is "1s".`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
227
|
+
}
|
|
228
|
+
// Validate refreshTokenExpiry format if provided
|
|
229
|
+
if (config.refreshTokenExpiry !== undefined && !isValidTimespan(config.refreshTokenExpiry)) {
|
|
230
|
+
throw new AuthError(`Invalid refreshTokenExpiry "${config.refreshTokenExpiry}". ` +
|
|
231
|
+
`Use formats like "15m", "1h", "7d". Minimum is "1s".`, 400, 'INVALID_TOKEN_EXPIRY');
|
|
232
|
+
}
|
|
233
|
+
// Store config with defaults
|
|
234
|
+
const accessExpiry = config.accessTokenExpiry ?? DEFAULT_ACCESS_EXPIRY;
|
|
235
|
+
const refreshExpiry = config.refreshTokenExpiry ?? DEFAULT_REFRESH_EXPIRY;
|
|
236
|
+
// Validate expiration bounds (Security Phase 3.1)
|
|
237
|
+
// This prevents developers from setting insecure expiration times
|
|
238
|
+
validateTokenExpiration(accessExpiry, refreshExpiry);
|
|
239
|
+
this.config = {
|
|
240
|
+
...config,
|
|
241
|
+
accessTokenExpiry: accessExpiry,
|
|
242
|
+
refreshTokenExpiry: refreshExpiry,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Creates a JWT token with the given payload
|
|
247
|
+
*
|
|
248
|
+
* @param payload - Token payload (sub, email, type required)
|
|
249
|
+
* @param expiresIn - Expiration time string (e.g., '15m', '7d')
|
|
250
|
+
* @param options - Additional options
|
|
251
|
+
* @param options.notBefore - Delay in seconds before token becomes valid (default: 0)
|
|
252
|
+
*/
|
|
253
|
+
createToken(payload, expiresIn, options) {
|
|
254
|
+
const now = Math.floor(Date.now() / 1000);
|
|
255
|
+
const exp = now + parseTimeToSeconds(expiresIn);
|
|
256
|
+
// Security Phase 3.3: Add not-before (nbf) claim
|
|
257
|
+
// nbf = issued at + optional delay (default: 0, meaning valid immediately)
|
|
258
|
+
const nbf = now + (options?.notBefore ?? 0);
|
|
259
|
+
const fullPayload = {
|
|
260
|
+
...payload,
|
|
261
|
+
iat: now,
|
|
262
|
+
exp,
|
|
263
|
+
nbf, // Token is not valid before this time
|
|
264
|
+
};
|
|
265
|
+
// Create header
|
|
266
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
267
|
+
const encodedHeader = base64urlEncode(JSON.stringify(header));
|
|
268
|
+
const encodedPayload = base64urlEncode(JSON.stringify(fullPayload));
|
|
269
|
+
// Create signature
|
|
270
|
+
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
271
|
+
const signature = createSignature(signatureInput, this.config.secret);
|
|
272
|
+
return `${signatureInput}.${signature}`;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Verifies a JWT token and returns the payload
|
|
276
|
+
*
|
|
277
|
+
* @throws AuthError if token is invalid or expired
|
|
278
|
+
*/
|
|
279
|
+
verifyToken(token) {
|
|
280
|
+
const parts = token.split('.');
|
|
281
|
+
if (parts.length !== 3) {
|
|
282
|
+
throw new AuthError('Invalid token format', 401, 'INVALID_TOKEN');
|
|
283
|
+
}
|
|
284
|
+
const [encodedHeader, encodedPayload, signature] = parts;
|
|
285
|
+
// Critical Fix #2: Validate algorithm BEFORE signature verification
|
|
286
|
+
// This prevents algorithm confusion attacks (CVE-2015-9235)
|
|
287
|
+
let header;
|
|
288
|
+
try {
|
|
289
|
+
header = JSON.parse(base64urlDecode(encodedHeader));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
throw new AuthError('Invalid token header', 401, 'INVALID_TOKEN');
|
|
293
|
+
}
|
|
294
|
+
// Only allow HS256 - reject "none", RS256, and other algorithms
|
|
295
|
+
if (header.alg !== 'HS256') {
|
|
296
|
+
throw new AuthError(`Invalid algorithm: ${header.alg}. Only HS256 is supported.`, 401, 'INVALID_TOKEN');
|
|
297
|
+
}
|
|
298
|
+
if (header.typ !== 'JWT') {
|
|
299
|
+
throw new AuthError('Invalid token type in header', 401, 'INVALID_TOKEN');
|
|
300
|
+
}
|
|
301
|
+
// Verify signature using timing-safe comparison to prevent timing attacks
|
|
302
|
+
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
303
|
+
const expectedSignature = createSignature(signatureInput, this.config.secret);
|
|
304
|
+
const sigBuffer = Buffer.from(signature, 'utf8');
|
|
305
|
+
const expectedBuffer = Buffer.from(expectedSignature, 'utf8');
|
|
306
|
+
if (sigBuffer.length !== expectedBuffer.length || !timingSafeEqual(sigBuffer, expectedBuffer)) {
|
|
307
|
+
throw new AuthError('Invalid token signature', 401, 'INVALID_TOKEN');
|
|
308
|
+
}
|
|
309
|
+
// Decode payload
|
|
310
|
+
let payload;
|
|
311
|
+
try {
|
|
312
|
+
const decoded = JSON.parse(base64urlDecode(encodedPayload));
|
|
313
|
+
// Validate required fields
|
|
314
|
+
if (typeof decoded.sub !== 'string' ||
|
|
315
|
+
typeof decoded.email !== 'string' ||
|
|
316
|
+
typeof decoded.iat !== 'number' ||
|
|
317
|
+
typeof decoded.exp !== 'number' ||
|
|
318
|
+
(decoded.type !== 'access' && decoded.type !== 'refresh')) {
|
|
319
|
+
throw new AuthError('Missing required token fields', 401, 'INVALID_TOKEN');
|
|
320
|
+
}
|
|
321
|
+
payload = decoded;
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (error instanceof AuthError) {
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
throw new AuthError(error instanceof Error ? error.message : 'Invalid token payload', 401, 'INVALID_TOKEN');
|
|
328
|
+
}
|
|
329
|
+
// Check expiration
|
|
330
|
+
const now = Math.floor(Date.now() / 1000);
|
|
331
|
+
if (payload.exp < now) {
|
|
332
|
+
throw new AuthError('Token has expired', 401, 'TOKEN_EXPIRED');
|
|
333
|
+
}
|
|
334
|
+
// Check not-before claim if present (Medium Fix #10)
|
|
335
|
+
if (typeof payload.nbf === 'number' && payload.nbf > now) {
|
|
336
|
+
throw new AuthError('Token not yet valid', 401, 'TOKEN_NOT_YET_VALID');
|
|
337
|
+
}
|
|
338
|
+
// Verify issuer if configured
|
|
339
|
+
if (this.config.issuer && payload.iss !== this.config.issuer) {
|
|
340
|
+
throw new AuthError('Invalid token issuer', 401, 'INVALID_TOKEN');
|
|
341
|
+
}
|
|
342
|
+
// Verify audience if configured
|
|
343
|
+
if (this.config.audience && payload.aud !== this.config.audience) {
|
|
344
|
+
throw new AuthError('Invalid token audience', 401, 'INVALID_TOKEN');
|
|
345
|
+
}
|
|
346
|
+
return payload;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Creates an access/refresh token pair for a user
|
|
350
|
+
*
|
|
351
|
+
* @param user - The user to create tokens for
|
|
352
|
+
* @param additionalClaims - Custom claims to include (cannot override reserved claims)
|
|
353
|
+
* @throws AuthError if additionalClaims contains reserved JWT claims
|
|
354
|
+
*/
|
|
355
|
+
createTokenPair(user, additionalClaims) {
|
|
356
|
+
// Critical Fix #3: Validate additionalClaims don't contain reserved claims
|
|
357
|
+
if (additionalClaims) {
|
|
358
|
+
for (const key of Object.keys(additionalClaims)) {
|
|
359
|
+
if (RESERVED_JWT_CLAIMS.has(key)) {
|
|
360
|
+
throw new AuthError(`Cannot override reserved JWT claim: ${key}. ` +
|
|
361
|
+
`Reserved claims are: ${[...RESERVED_JWT_CLAIMS].join(', ')}`, 400, 'INVALID_CLAIMS');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const tokenId = generateTokenId();
|
|
366
|
+
const basePayload = {
|
|
367
|
+
sub: user.id,
|
|
368
|
+
email: user.email,
|
|
369
|
+
jti: tokenId,
|
|
370
|
+
...(this.config.issuer && { iss: this.config.issuer }),
|
|
371
|
+
...(this.config.audience && { aud: this.config.audience }),
|
|
372
|
+
...additionalClaims,
|
|
373
|
+
};
|
|
374
|
+
const accessToken = this.createToken({ ...basePayload, type: 'access' }, this.config.accessTokenExpiry);
|
|
375
|
+
const refreshToken = this.createToken({ ...basePayload, type: 'refresh' }, this.config.refreshTokenExpiry);
|
|
376
|
+
return {
|
|
377
|
+
accessToken,
|
|
378
|
+
refreshToken,
|
|
379
|
+
expiresIn: parseTimeToSeconds(this.config.accessTokenExpiry),
|
|
380
|
+
tokenType: 'Bearer',
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
refreshTokens(refreshToken, userLoader) {
|
|
384
|
+
const payload = this.verifyToken(refreshToken);
|
|
385
|
+
if (payload.type !== 'refresh') {
|
|
386
|
+
throw new AuthError('Invalid token type: expected refresh token', 401, 'INVALID_TOKEN');
|
|
387
|
+
}
|
|
388
|
+
// If userLoader provided, fetch fresh user data
|
|
389
|
+
if (userLoader) {
|
|
390
|
+
return userLoader(payload.sub).then((user) => {
|
|
391
|
+
if (!user) {
|
|
392
|
+
throw new AuthError('User not found', 401, 'USER_NOT_FOUND');
|
|
393
|
+
}
|
|
394
|
+
return this.createTokenPair(user);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
// Otherwise, create new tokens from payload data
|
|
398
|
+
const user = {
|
|
399
|
+
id: payload.sub,
|
|
400
|
+
email: payload.email,
|
|
401
|
+
};
|
|
402
|
+
return this.createTokenPair(user);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Decodes a token without verification
|
|
406
|
+
* Useful for extracting payload from expired tokens
|
|
407
|
+
*/
|
|
408
|
+
decodeToken(token) {
|
|
409
|
+
try {
|
|
410
|
+
const parts = token.split('.');
|
|
411
|
+
if (parts.length !== 3) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
return JSON.parse(base64urlDecode(parts[1]));
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Extracts token from Authorization header
|
|
422
|
+
* Supports 'Bearer <token>' format
|
|
423
|
+
*/
|
|
424
|
+
extractFromHeader(authHeader) {
|
|
425
|
+
if (!authHeader) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
const parts = authHeader.split(' ');
|
|
429
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
return parts[1];
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Creates a new JWT manager instance (succinct API)
|
|
437
|
+
*/
|
|
438
|
+
export function jwtManager(config) {
|
|
439
|
+
return new JwtManager(config);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Creates a new JWT manager instance
|
|
443
|
+
*
|
|
444
|
+
* @deprecated Use `jwtManager()` instead. Will be removed in v0.9.
|
|
445
|
+
*/
|
|
446
|
+
export const createJwtManager = jwtManager;
|
|
447
|
+
/**
|
|
448
|
+
* Creates an in-memory token store for development and testing
|
|
449
|
+
*
|
|
450
|
+
* ⚠️ WARNING: NOT suitable for production!
|
|
451
|
+
* - Does not persist across server restarts
|
|
452
|
+
* - Does not work across multiple server instances
|
|
453
|
+
* - No automatic cleanup of expired token IDs
|
|
454
|
+
*
|
|
455
|
+
* For production, use Redis or database-backed storage:
|
|
456
|
+
* - upstash/redis for serverless
|
|
457
|
+
* - ioredis for traditional servers
|
|
458
|
+
* - Database table for audit trail
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* // Development/Testing
|
|
463
|
+
* const tokenStore = createInMemoryTokenStore();
|
|
464
|
+
*
|
|
465
|
+
* const authConfig: AuthConfig = {
|
|
466
|
+
* jwt: { secret: process.env.JWT_SECRET! },
|
|
467
|
+
* isTokenRevoked: tokenStore.isRevoked,
|
|
468
|
+
* };
|
|
469
|
+
*
|
|
470
|
+
* // Revoke on logout
|
|
471
|
+
* app.post('/logout', async (req) => {
|
|
472
|
+
* const tokenId = req.auth.token.jti;
|
|
473
|
+
* tokenStore.revoke(tokenId);
|
|
474
|
+
* });
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
export function createInMemoryTokenStore() {
|
|
478
|
+
const revokedTokens = new Set();
|
|
479
|
+
return {
|
|
480
|
+
revoke: (tokenId) => {
|
|
481
|
+
revokedTokens.add(tokenId);
|
|
482
|
+
},
|
|
483
|
+
isRevoked: (tokenId) => revokedTokens.has(tokenId),
|
|
484
|
+
clear: () => {
|
|
485
|
+
revokedTokens.clear();
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=jwt.js.map
|
package/dist/jwt.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGvE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,MAAM,qBAAqB,GAAG,KAAK,CAAC;AACpC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;GAEG;AACH,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAEpC,+EAA+E;AAC/E,+CAA+C;AAC/C,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAEpC;;;GAGG;AACH,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,CAAC;AAEzC;;;GAGG;AACH,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,CAAC;AAE1C;;;GAGG;AACH,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAEpD;;;GAGG;AACH,MAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,CAAC;AAE/C;;GAEG;AACH,MAAM,+BAA+B,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAEzD;;GAEG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,OAAO;CACR,CAAC,CAAC;AAEH,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,2BAA2B;IAC3B,OAAO,KAAK,IAAI,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CACjB,wBAAwB,IAAI,qCAAqC,EACjE,GAAG,EACH,qBAAqB,CACtB,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAEtB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG;YACN,OAAO,KAAK,CAAC;QACf,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,CAAC;QACpB,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACzB,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC9B;YACE,MAAM,IAAI,SAAS,CAAC,sBAAsB,IAAI,EAAE,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,IAAqB;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,IAAY,EAAE,MAAc;IACnD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAAoB,EAAE,aAAqB;IACjF,MAAM,aAAa,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAEzD,+BAA+B;IAC/B,IAAI,aAAa,GAAG,wBAAwB,EAAE,CAAC;QAC7C,MAAM,IAAI,SAAS,CACjB,wBAAwB,YAAY,MAAM,aAAa,yBAAyB;YAC9E,GAAG,wBAAwB,4DAA4D,EACzF,GAAG,EACH,sBAAsB,CACvB,CAAC;IACJ,CAAC;IAED,IAAI,aAAa,GAAG,wBAAwB,EAAE,CAAC;QAC7C,MAAM,IAAI,SAAS,CACjB,wBAAwB,YAAY,MAAM,aAAa,wBAAwB;YAC7E,GAAG,wBAAwB,2DAA2D,EACxF,GAAG,EACH,sBAAsB,CACvB,CAAC;IACJ,CAAC;IAED,gCAAgC;IAChC,IAAI,cAAc,GAAG,yBAAyB,EAAE,CAAC;QAC/C,MAAM,IAAI,SAAS,CACjB,yBAAyB,aAAa,MAAM,cAAc,yBAAyB;YACjF,GAAG,yBAAyB,yDAAyD,EACvF,GAAG,EACH,sBAAsB,CACvB,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,GAAG,yBAAyB,EAAE,CAAC;QAC/C,MAAM,IAAI,SAAS,CACjB,yBAAyB,aAAa,MAAM,cAAc,wBAAwB;YAChF,GAAG,yBAAyB,gEAAgE,EAC9F,GAAG,EACH,sBAAsB,CACvB,CAAC;IACJ,CAAC;IAED,sDAAsD;IACtD,IAAI,aAAa,GAAG,8BAA8B,EAAE,CAAC;QACnD,OAAO,CAAC,IAAI,CACV,mCAAmC,YAAY,+CAA+C;YAC5F,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,GAAG,+BAA+B,EAAE,CAAC;QACrD,OAAO,CAAC,IAAI,CACV,oCAAoC,aAAa,2CAA2C;YAC1F,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IAED,8CAA8C;IAC9C,IAAI,cAAc,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,yBAAyB,aAAa,MAAM,cAAc,yBAAyB;YACjF,wBAAwB,YAAY,MAAM,aAAa,KAAK,EAC9D,GAAG,EACH,sBAAsB,CACvB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,UAAU;IACJ,MAAM,CAGX;IAEZ,YAAY,MAAiB;QAC3B,2CAA2C;QAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YAC/D,MAAM,IAAI,SAAS,CACjB,+BAA+B,iBAAiB,+BAA+B;gBAC7E,wCAAwC,EAC1C,GAAG,EACH,oBAAoB,CACrB,CAAC;QACJ,CAAC;QAED,mEAAmE;QACnE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;QAChD,IAAI,WAAW,GAAG,wBAAwB,EAAE,CAAC;YAC3C,MAAM,IAAI,SAAS,CACjB,6CAA6C,WAAW,uBAAuB;gBAC7E,uEAAuE,EACzE,GAAG,EACH,oBAAoB,CACrB,CAAC;QACJ,CAAC;QAED,gDAAgD;QAChD,IAAI,MAAM,CAAC,iBAAiB,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACzF,MAAM,IAAI,SAAS,CACjB,8BAA8B,MAAM,CAAC,iBAAiB,KAAK;gBACzD,sDAAsD,EACxD,GAAG,EACH,sBAAsB,CACvB,CAAC;QACJ,CAAC;QAED,iDAAiD;QACjD,IAAI,MAAM,CAAC,kBAAkB,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC3F,MAAM,IAAI,SAAS,CACjB,+BAA+B,MAAM,CAAC,kBAAkB,KAAK;gBAC3D,sDAAsD,EACxD,GAAG,EACH,sBAAsB,CACvB,CAAC;QACJ,CAAC;QAED,6BAA6B;QAC7B,MAAM,YAAY,GAAG,MAAM,CAAC,iBAAiB,IAAI,qBAAqB,CAAC;QACvE,MAAM,aAAa,GAAG,MAAM,CAAC,kBAAkB,IAAI,sBAAsB,CAAC;QAE1E,kDAAkD;QAClD,kEAAkE;QAClE,uBAAuB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QAErD,IAAI,CAAC,MAAM,GAAG;YACZ,GAAG,MAAM;YACT,iBAAiB,EAAE,YAAY;YAC/B,kBAAkB,EAAE,aAAa;SAClC,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,WAAW,CACT,OAIC,EACD,SAAiB,EACjB,OAAgC;QAEhC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,GAAG,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAEhD,iDAAiD;QACjD,2EAA2E;QAC3E,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC;QAE5C,MAAM,WAAW,GAAiB;YAChC,GAAG,OAAO;YACV,GAAG,EAAE,GAAG;YACR,GAAG;YACH,GAAG,EAAE,sCAAsC;SAC5C,CAAC;QAEF,gBAAgB;QAChB,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QAC5C,MAAM,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9D,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;QAEpE,mBAAmB;QACnB,MAAM,cAAc,GAAG,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC;QAC5D,MAAM,SAAS,GAAG,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEtE,OAAO,GAAG,cAAc,IAAI,SAAS,EAAE,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,KAAa;QACvB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,sBAAsB,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,CAAC,aAAa,EAAE,cAAc,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;QAEzD,oEAAoE;QACpE,4DAA4D;QAC5D,IAAI,MAAoC,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,aAAa,CAAC,CAAiC,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,SAAS,CAAC,sBAAsB,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACpE,CAAC;QAED,gEAAgE;QAChE,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,SAAS,CACjB,sBAAsB,MAAM,CAAC,GAAG,4BAA4B,EAC5D,GAAG,EACH,eAAe,CAChB,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,SAAS,CAAC,8BAA8B,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QAC5E,CAAC;QAED,0EAA0E;QAC1E,MAAM,cAAc,GAAG,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC;QAC5D,MAAM,iBAAiB,GAAG,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAE9E,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAE9D,IAAI,SAAS,CAAC,MAAM,KAAK,cAAc,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,CAAC;YAC9F,MAAM,IAAI,SAAS,CAAC,yBAAyB,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACvE,CAAC;QAED,iBAAiB;QACjB,IAAI,OAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,CAA4B,CAAC;YAEvF,2BAA2B;YAC3B,IACE,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;gBAC/B,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ;gBACjC,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;gBAC/B,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;gBAC/B,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,EACzD,CAAC;gBACD,MAAM,IAAI,SAAS,CAAC,+BAA+B,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;YAC7E,CAAC;YAED,OAAO,GAAG,OAAuB,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;gBAC/B,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,SAAS,CACjB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAChE,GAAG,EACH,eAAe,CAChB,CAAC;QACJ,CAAC;QAED,mBAAmB;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;YACtB,MAAM,IAAI,SAAS,CAAC,mBAAmB,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACjE,CAAC;QAED,qDAAqD;QACrD,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;YACzD,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,GAAG,EAAE,qBAAqB,CAAC,CAAC;QACzE,CAAC;QAED,8BAA8B;QAC9B,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7D,MAAM,IAAI,SAAS,CAAC,sBAAsB,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACpE,CAAC;QAED,gCAAgC;QAChC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACjE,MAAM,IAAI,SAAS,CAAC,wBAAwB,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACtE,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;OAMG;IACH,eAAe,CAAC,IAAU,EAAE,gBAA0C;QACpE,2EAA2E;QAC3E,IAAI,gBAAgB,EAAE,CAAC;YACrB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBAChD,IAAI,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBACjC,MAAM,IAAI,SAAS,CACjB,uCAAuC,GAAG,IAAI;wBAC5C,wBAAwB,CAAC,GAAG,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAC/D,GAAG,EACH,gBAAgB,CACjB,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;QAElC,MAAM,WAAW,GAAG;YAClB,GAAG,EAAE,IAAI,CAAC,EAAE;YACZ,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,OAAO;YACZ,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACtD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC1D,GAAG,gBAAgB;SACpB,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAClC,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,EAClC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAC9B,CAAC;QAEF,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CACnC,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,EACnC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAC/B,CAAC;QAEF,OAAO;YACL,WAAW;YACX,YAAY;YACZ,SAAS,EAAE,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC;YAC5D,SAAS,EAAE,QAAQ;SACpB,CAAC;IACJ,CAAC;IAYD,aAAa,CACX,YAAoB,EACpB,UAAqD;QAErD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;QAE/C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,SAAS,CAAC,4CAA4C,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QAC1F,CAAC;QAED,gDAAgD;QAChD,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;gBAC3C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,MAAM,IAAI,SAAS,CAAC,gBAAgB,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC;gBAC/D,CAAC;gBACD,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YACpC,CAAC,CAAC,CAAC;QACL,CAAC;QAED,iDAAiD;QACjD,MAAM,IAAI,GAAS;YACjB,EAAE,EAAE,OAAO,CAAC,GAAG;YACf,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB,CAAC;QAEF,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,KAAa;QACvB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAiB,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,UAA8B;QAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAkB3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,wBAAwB;IACtC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IAExC,OAAO;QACL,MAAM,EAAE,CAAC,OAAe,EAAE,EAAE;YAC1B,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QACD,SAAS,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;QAC1D,KAAK,EAAE,GAAG,EAAE;YACV,aAAa,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication middleware for @veloxts/auth
|
|
3
|
+
* @module auth/middleware
|
|
4
|
+
*/
|
|
5
|
+
import type { BaseContext } from '@veloxts/core';
|
|
6
|
+
import type { MiddlewareFunction } from '@veloxts/router';
|
|
7
|
+
import { JwtManager } from './jwt.js';
|
|
8
|
+
import type { AuthConfig, AuthContext, AuthMiddlewareOptions, GuardDefinition, User } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Creates an authentication middleware for procedures (succinct API)
|
|
11
|
+
*
|
|
12
|
+
* This middleware:
|
|
13
|
+
* 1. Extracts JWT from Authorization header
|
|
14
|
+
* 2. Verifies the token
|
|
15
|
+
* 3. Loads user from database (if userLoader provided)
|
|
16
|
+
* 4. Adds user and auth context to ctx
|
|
17
|
+
* 5. Runs guards if specified
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const auth = authMiddleware(authConfig);
|
|
22
|
+
*
|
|
23
|
+
* // Use in procedures
|
|
24
|
+
* const getProfile = procedure()
|
|
25
|
+
* .use(auth.middleware())
|
|
26
|
+
* .query(async ({ ctx }) => {
|
|
27
|
+
* return ctx.user; // Guaranteed to exist
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Optional auth (user may be undefined)
|
|
31
|
+
* const getPosts = procedure()
|
|
32
|
+
* .use(auth.middleware({ optional: true }))
|
|
33
|
+
* .query(async ({ ctx }) => {
|
|
34
|
+
* // ctx.user may be undefined
|
|
35
|
+
* return fetchPosts(ctx.user?.id);
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // With guards
|
|
39
|
+
* const adminOnly = procedure()
|
|
40
|
+
* .use(auth.middleware({ guards: [hasRole('admin')] }))
|
|
41
|
+
* .query(async ({ ctx }) => {
|
|
42
|
+
* // Only admins get here
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function authMiddleware(config: AuthConfig): {
|
|
47
|
+
middleware: <TInput, TContext extends BaseContext, TOutput>(options?: AuthMiddlewareOptions) => MiddlewareFunction<TInput, TContext, TContext & {
|
|
48
|
+
user?: User;
|
|
49
|
+
auth: AuthContext;
|
|
50
|
+
}, TOutput>;
|
|
51
|
+
requireAuth: <TInput, TContext extends BaseContext, TOutput>(guards?: Array<GuardDefinition | string>) => MiddlewareFunction<TInput, TContext, TContext & {
|
|
52
|
+
user: User;
|
|
53
|
+
auth: AuthContext;
|
|
54
|
+
}, TOutput>;
|
|
55
|
+
optionalAuth: <TInput, TContext extends BaseContext, TOutput>() => MiddlewareFunction<TInput, TContext, TContext & {
|
|
56
|
+
user?: User;
|
|
57
|
+
auth: AuthContext;
|
|
58
|
+
}, TOutput>;
|
|
59
|
+
jwt: JwtManager;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Creates an authentication middleware for procedures
|
|
63
|
+
*
|
|
64
|
+
* @deprecated Use `authMiddleware()` instead. Will be removed in v0.9.
|
|
65
|
+
*/
|
|
66
|
+
export declare const createAuthMiddleware: typeof authMiddleware;
|
|
67
|
+
/**
|
|
68
|
+
* Creates a rate limiting middleware (succinct API)
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const rateLimit = rateLimitMiddleware({
|
|
73
|
+
* max: 100,
|
|
74
|
+
* windowMs: 60000, // 1 minute
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* const login = procedure()
|
|
78
|
+
* .use(rateLimit)
|
|
79
|
+
* .input(LoginSchema)
|
|
80
|
+
* .mutation(handler);
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function rateLimitMiddleware<TInput, TContext extends BaseContext, TOutput>(options: {
|
|
84
|
+
max?: number;
|
|
85
|
+
windowMs?: number;
|
|
86
|
+
keyGenerator?: (ctx: TContext) => string;
|
|
87
|
+
message?: string;
|
|
88
|
+
}): MiddlewareFunction<TInput, TContext, TContext, TOutput>;
|
|
89
|
+
/**
|
|
90
|
+
* Creates a rate limiting middleware
|
|
91
|
+
*
|
|
92
|
+
* @deprecated Use `rateLimitMiddleware()` instead. Will be removed in v0.9.
|
|
93
|
+
*/
|
|
94
|
+
export declare const createRateLimitMiddleware: typeof rateLimitMiddleware;
|
|
95
|
+
/**
|
|
96
|
+
* Clears rate limit store (useful for testing)
|
|
97
|
+
*/
|
|
98
|
+
export declare function clearRateLimitStore(): void;
|
|
99
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,eAAe,EAEf,IAAI,EACL,MAAM,YAAY,CAAC;AAOpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU;iBAM3B,MAAM,EAAE,QAAQ,SAAS,WAAW,EAAE,OAAO,YACtD,qBAAqB,KAC7B,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG;QAAE,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,EAAE,OAAO,CAAC;kBA8H1E,MAAM,EAAE,QAAQ,SAAS,WAAW,EAAE,OAAO,WACvD,KAAK,CAAC,eAAe,GAAG,MAAM,CAAC,KACvC,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,EAAE,OAAO,CAAC;mBAYxE,MAAM,EAAE,QAAQ,SAAS,WAAW,EAAE,OAAO,OAAK,kBAAkB,CACxF,MAAM,EACN,QAAQ,EACR,QAAQ,GAAG;QAAE,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,EAC7C,OAAO,CACR;;EAUF;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,uBAAiB,CAAC;AAkBnD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,SAAS,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE;IAC1F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAuC1D;AAED;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,4BAAsB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|