@veloxts/auth 0.6.69 → 0.6.71
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/CHANGELOG.md +49 -0
- package/dist/adapter.d.ts +10 -4
- package/dist/adapter.js +15 -9
- package/dist/adapters/index.d.ts +26 -6
- package/dist/adapters/index.js +25 -7
- package/dist/adapters/jwt-adapter.d.ts +261 -0
- package/dist/adapters/jwt-adapter.js +360 -0
- package/dist/decoration.d.ts +88 -0
- package/dist/decoration.js +112 -0
- package/dist/index.d.ts +7 -8
- package/dist/index.js +6 -1
- package/dist/middleware.d.ts +4 -4
- package/dist/middleware.js +7 -1
- package/dist/plugin.d.ts +70 -5
- package/dist/plugin.js +172 -62
- package/dist/providers.js +3 -1
- package/dist/types.d.ts +70 -33
- package/package.json +5 -5
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Authentication Adapter for @veloxts/auth
|
|
3
|
+
*
|
|
4
|
+
* Implements the AuthAdapter interface using JWT tokens.
|
|
5
|
+
* This allows JWT auth to follow the same pattern as external providers,
|
|
6
|
+
* enabling easy swapping between authentication strategies.
|
|
7
|
+
*
|
|
8
|
+
* @module auth/adapters/jwt-adapter
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
13
|
+
* import { createJwtAdapter, jwtAuth } from '@veloxts/auth/adapters/jwt-adapter';
|
|
14
|
+
*
|
|
15
|
+
* // Option 1: Using createJwtAdapter + createAuthAdapterPlugin
|
|
16
|
+
* const { adapter, config } = createJwtAdapter({
|
|
17
|
+
* jwt: {
|
|
18
|
+
* secret: process.env.JWT_SECRET!,
|
|
19
|
+
* accessTokenExpiry: '15m',
|
|
20
|
+
* refreshTokenExpiry: '7d',
|
|
21
|
+
* },
|
|
22
|
+
* userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const authPlugin = createAuthAdapterPlugin({ adapter, config });
|
|
26
|
+
* app.use(authPlugin);
|
|
27
|
+
*
|
|
28
|
+
* // Option 2: Using jwtAuth convenience function (recommended)
|
|
29
|
+
* import { jwtAuth } from '@veloxts/auth';
|
|
30
|
+
*
|
|
31
|
+
* app.use(jwtAuth({
|
|
32
|
+
* jwt: {
|
|
33
|
+
* secret: process.env.JWT_SECRET!,
|
|
34
|
+
* accessTokenExpiry: '15m',
|
|
35
|
+
* refreshTokenExpiry: '7d',
|
|
36
|
+
* },
|
|
37
|
+
* userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
|
|
38
|
+
* }));
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import { AuthAdapterError, BaseAuthAdapter } from '../adapter.js';
|
|
42
|
+
import { createInMemoryTokenStore, JwtManager } from '../jwt.js';
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// JWT Adapter Implementation
|
|
45
|
+
// ============================================================================
|
|
46
|
+
/**
|
|
47
|
+
* JWT Authentication Adapter
|
|
48
|
+
*
|
|
49
|
+
* Implements the AuthAdapter interface using JWT tokens.
|
|
50
|
+
* Provides session loading from Authorization headers and
|
|
51
|
+
* optional routes for token refresh and logout.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const adapter = new JwtAdapter();
|
|
56
|
+
* await adapter.initialize(fastify, {
|
|
57
|
+
* name: 'jwt',
|
|
58
|
+
* jwt: { secret: process.env.JWT_SECRET! },
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Get session from request
|
|
62
|
+
* const session = await adapter.getSession(request);
|
|
63
|
+
* if (session) {
|
|
64
|
+
* console.log('User:', session.user.email);
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export class JwtAdapter extends BaseAuthAdapter {
|
|
69
|
+
jwt = null;
|
|
70
|
+
tokenStore = null;
|
|
71
|
+
userLoader;
|
|
72
|
+
enableRoutes = true;
|
|
73
|
+
routePrefix = '/api/auth';
|
|
74
|
+
constructor() {
|
|
75
|
+
super('jwt', '1.0.0');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Initialize the adapter with JWT configuration
|
|
79
|
+
*
|
|
80
|
+
* Sets up the JwtManager, token store, and configuration options.
|
|
81
|
+
* Also exposes `jwtManager` and `tokenStore` on the Fastify instance.
|
|
82
|
+
*/
|
|
83
|
+
async initialize(fastify, config) {
|
|
84
|
+
await super.initialize(fastify, config);
|
|
85
|
+
if (!config.jwt) {
|
|
86
|
+
throw new AuthAdapterError('JWT configuration is required in adapter config', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
87
|
+
}
|
|
88
|
+
// Initialize JWT manager
|
|
89
|
+
this.jwt = new JwtManager(config.jwt);
|
|
90
|
+
/**
|
|
91
|
+
* Initialize token store (default: in-memory with warning)
|
|
92
|
+
*
|
|
93
|
+
* @example Production Redis store passed in config:
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { createRedisTokenStore } from '@veloxts/auth/redis';
|
|
96
|
+
* …
|
|
97
|
+
* tokenStore: createRedisTokenStore({ url: process.env.REDIS_URL })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
this.tokenStore = config.tokenStore ?? createInMemoryTokenStore();
|
|
101
|
+
if (!config.tokenStore) {
|
|
102
|
+
this.debug('Using in-memory token store. Use Redis in production.');
|
|
103
|
+
}
|
|
104
|
+
this.userLoader = config.userLoader;
|
|
105
|
+
this.enableRoutes = config.enableRoutes ?? true;
|
|
106
|
+
this.routePrefix = config.routePrefix ?? '/api/auth';
|
|
107
|
+
// Expose JWT manager and token store on fastify for direct access
|
|
108
|
+
if (!fastify.hasDecorator('jwtManager')) {
|
|
109
|
+
fastify.decorate('jwtManager', this.jwt);
|
|
110
|
+
}
|
|
111
|
+
if (!fastify.hasDecorator('tokenStore')) {
|
|
112
|
+
fastify.decorate('tokenStore', this.tokenStore);
|
|
113
|
+
}
|
|
114
|
+
this.info('JWT adapter initialized');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get session from JWT token in Authorization header
|
|
118
|
+
*
|
|
119
|
+
* Extracts and verifies the JWT token, checks revocation status,
|
|
120
|
+
* and loads the user if a userLoader is configured.
|
|
121
|
+
*
|
|
122
|
+
* @returns Session result with user and session data, or null if not authenticated
|
|
123
|
+
*/
|
|
124
|
+
async getSession(request) {
|
|
125
|
+
if (!this.jwt || !this.tokenStore) {
|
|
126
|
+
throw new AuthAdapterError('JWT adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
127
|
+
}
|
|
128
|
+
// Extract token from Authorization header
|
|
129
|
+
const authHeader = request.headers.authorization;
|
|
130
|
+
const token = this.jwt.extractFromHeader(authHeader);
|
|
131
|
+
if (!token) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
// Verify token
|
|
136
|
+
const payload = this.jwt.verifyToken(token);
|
|
137
|
+
// Check for access token type
|
|
138
|
+
if (payload.type !== 'access') {
|
|
139
|
+
this.debug('Non-access token in Authorization header');
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// Check if token is revoked
|
|
143
|
+
if (payload.jti) {
|
|
144
|
+
const isRevoked = await this.tokenStore.isRevoked(payload.jti);
|
|
145
|
+
if (isRevoked) {
|
|
146
|
+
this.debug('Token has been revoked');
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Load user if loader provided
|
|
151
|
+
let user;
|
|
152
|
+
if (this.userLoader) {
|
|
153
|
+
user = await this.userLoader(payload.sub);
|
|
154
|
+
if (!user) {
|
|
155
|
+
this.debug('User not found for token');
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
user = { id: payload.sub, email: payload.email };
|
|
161
|
+
}
|
|
162
|
+
// Store token and payload on request for middleware access
|
|
163
|
+
// Using type assertion to avoid modifying Fastify types
|
|
164
|
+
const requestWithJwt = request;
|
|
165
|
+
requestWithJwt.__jwtToken = token;
|
|
166
|
+
requestWithJwt.__jwtPayload = payload;
|
|
167
|
+
return {
|
|
168
|
+
user: {
|
|
169
|
+
id: user.id,
|
|
170
|
+
email: user.email,
|
|
171
|
+
name: user.name,
|
|
172
|
+
emailVerified: user.emailVerified,
|
|
173
|
+
providerData: { roles: user.roles, permissions: user.permissions },
|
|
174
|
+
},
|
|
175
|
+
session: {
|
|
176
|
+
sessionId: payload.jti ?? `jwt-${payload.sub}`,
|
|
177
|
+
userId: payload.sub,
|
|
178
|
+
expiresAt: payload.exp * 1000, // Convert to ms
|
|
179
|
+
isActive: true,
|
|
180
|
+
providerData: { token, payload },
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
this.debug(`Token verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get routes for token refresh and logout
|
|
191
|
+
*
|
|
192
|
+
* Returns routes only if `enableRoutes` is true in config.
|
|
193
|
+
*/
|
|
194
|
+
getRoutes() {
|
|
195
|
+
if (!this.enableRoutes) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
return [
|
|
199
|
+
// Refresh token endpoint
|
|
200
|
+
{
|
|
201
|
+
path: `${this.routePrefix}/refresh`,
|
|
202
|
+
methods: ['POST'],
|
|
203
|
+
handler: this.handleRefresh.bind(this),
|
|
204
|
+
description: 'Refresh access token using refresh token',
|
|
205
|
+
},
|
|
206
|
+
// Logout endpoint (revoke token)
|
|
207
|
+
{
|
|
208
|
+
path: `${this.routePrefix}/logout`,
|
|
209
|
+
methods: ['POST'],
|
|
210
|
+
handler: this.handleLogout.bind(this),
|
|
211
|
+
description: 'Revoke current access token',
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Handle token refresh requests
|
|
217
|
+
*
|
|
218
|
+
* Expects `refreshToken` in request body.
|
|
219
|
+
* Returns new token pair on success.
|
|
220
|
+
*/
|
|
221
|
+
async handleRefresh(request, reply) {
|
|
222
|
+
if (!this.jwt) {
|
|
223
|
+
throw new AuthAdapterError('JWT adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
224
|
+
}
|
|
225
|
+
const body = request.body;
|
|
226
|
+
const refreshToken = body?.refreshToken;
|
|
227
|
+
if (!refreshToken) {
|
|
228
|
+
reply.status(400).send({ error: 'Missing refreshToken in request body' });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const tokens = await this.jwt.refreshTokens(refreshToken, this.userLoader);
|
|
233
|
+
reply.send(tokens);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
reply.status(401).send({
|
|
237
|
+
error: error instanceof Error ? error.message : 'Token refresh failed',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Handle logout requests
|
|
243
|
+
*
|
|
244
|
+
* Revokes the current access token by adding its JTI to the token store.
|
|
245
|
+
* The token is extracted from the Authorization header.
|
|
246
|
+
*/
|
|
247
|
+
async handleLogout(request, reply) {
|
|
248
|
+
if (!this.tokenStore) {
|
|
249
|
+
throw new AuthAdapterError('JWT adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
250
|
+
}
|
|
251
|
+
// Get payload from request (set during getSession in preHandler)
|
|
252
|
+
const requestWithJwt = request;
|
|
253
|
+
const payload = requestWithJwt.__jwtPayload;
|
|
254
|
+
if (payload?.jti) {
|
|
255
|
+
await this.tokenStore.revoke(payload.jti);
|
|
256
|
+
this.debug(`Token ${payload.jti} revoked`);
|
|
257
|
+
}
|
|
258
|
+
reply.status(200).send({ success: true });
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Clean up adapter resources
|
|
262
|
+
*/
|
|
263
|
+
async cleanup() {
|
|
264
|
+
await super.cleanup();
|
|
265
|
+
this.jwt = null;
|
|
266
|
+
this.tokenStore = null;
|
|
267
|
+
this.userLoader = undefined;
|
|
268
|
+
this.info('JWT adapter cleaned up');
|
|
269
|
+
}
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Public API Methods
|
|
272
|
+
// ============================================================================
|
|
273
|
+
/**
|
|
274
|
+
* Create a token pair for a user
|
|
275
|
+
*
|
|
276
|
+
* Convenience method that delegates to the underlying JwtManager.
|
|
277
|
+
* Can be accessed via `fastify.jwtManager.createTokenPair()` as well.
|
|
278
|
+
*
|
|
279
|
+
* @param user - The user to create tokens for
|
|
280
|
+
* @param additionalClaims - Custom claims to include in the token
|
|
281
|
+
* @returns Token pair with access and refresh tokens
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* const tokens = adapter.createTokenPair(user);
|
|
286
|
+
* // { accessToken, refreshToken, expiresIn, tokenType }
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
createTokenPair(user, additionalClaims) {
|
|
290
|
+
if (!this.jwt) {
|
|
291
|
+
throw new AuthAdapterError('JWT adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
292
|
+
}
|
|
293
|
+
return this.jwt.createTokenPair(user, additionalClaims);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get the underlying JwtManager instance
|
|
297
|
+
*
|
|
298
|
+
* Useful for advanced token operations.
|
|
299
|
+
*/
|
|
300
|
+
getJwtManager() {
|
|
301
|
+
if (!this.jwt) {
|
|
302
|
+
throw new AuthAdapterError('JWT adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
303
|
+
}
|
|
304
|
+
return this.jwt;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get the token store instance
|
|
308
|
+
*
|
|
309
|
+
* Useful for manual token revocation.
|
|
310
|
+
*/
|
|
311
|
+
getTokenStore() {
|
|
312
|
+
if (!this.tokenStore) {
|
|
313
|
+
throw new AuthAdapterError('JWT adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
314
|
+
}
|
|
315
|
+
return this.tokenStore;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// Factory Function
|
|
320
|
+
// ============================================================================
|
|
321
|
+
/**
|
|
322
|
+
* Create a JWT auth adapter
|
|
323
|
+
*
|
|
324
|
+
* This is the recommended way to create a JWT adapter for use with
|
|
325
|
+
* createAuthAdapterPlugin. Returns both the adapter instance and
|
|
326
|
+
* the configuration for convenience.
|
|
327
|
+
*
|
|
328
|
+
* @param config - JWT adapter configuration (without name, which is auto-set to 'jwt')
|
|
329
|
+
* @returns Object with adapter and config
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* import { createJwtAdapter } from '@veloxts/auth/adapters/jwt-adapter';
|
|
334
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
335
|
+
*
|
|
336
|
+
* const { adapter, config } = createJwtAdapter({
|
|
337
|
+
* jwt: {
|
|
338
|
+
* secret: process.env.JWT_SECRET!,
|
|
339
|
+
* accessTokenExpiry: '15m',
|
|
340
|
+
* refreshTokenExpiry: '7d',
|
|
341
|
+
* },
|
|
342
|
+
* userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
|
|
343
|
+
* });
|
|
344
|
+
*
|
|
345
|
+
* const authPlugin = createAuthAdapterPlugin({ adapter, config });
|
|
346
|
+
* app.use(authPlugin);
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
export function createJwtAdapter(config) {
|
|
350
|
+
const adapter = new JwtAdapter();
|
|
351
|
+
const fullConfig = {
|
|
352
|
+
name: 'jwt',
|
|
353
|
+
...config,
|
|
354
|
+
};
|
|
355
|
+
return { adapter, config: fullConfig };
|
|
356
|
+
}
|
|
357
|
+
// ============================================================================
|
|
358
|
+
// Re-exports
|
|
359
|
+
// ============================================================================
|
|
360
|
+
export { AuthAdapterError } from '../adapter.js';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared decoration utilities for @veloxts/auth
|
|
3
|
+
*
|
|
4
|
+
* This module provides common functionality for decorating Fastify instances
|
|
5
|
+
* and requests with authentication state, shared between the native auth plugin
|
|
6
|
+
* and external auth adapters.
|
|
7
|
+
*
|
|
8
|
+
* @module auth/decoration
|
|
9
|
+
*/
|
|
10
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
11
|
+
import type { AuthContext, User } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Symbol used to mark a Fastify instance as having auth already registered.
|
|
14
|
+
*
|
|
15
|
+
* This prevents double-registration of conflicting auth systems (e.g., using
|
|
16
|
+
* both authPlugin and an AuthAdapter on the same server).
|
|
17
|
+
*/
|
|
18
|
+
export declare const AUTH_REGISTERED: unique symbol;
|
|
19
|
+
/**
|
|
20
|
+
* Checks for double-registration of auth systems and throws if detected.
|
|
21
|
+
*
|
|
22
|
+
* Call this at the start of both `authPlugin` and `createAuthAdapterPlugin`
|
|
23
|
+
* registration to ensure only one auth system is active.
|
|
24
|
+
*
|
|
25
|
+
* @param fastify - Fastify server instance
|
|
26
|
+
* @param source - Identifier for the auth system being registered (e.g., 'authPlugin', 'adapter:better-auth')
|
|
27
|
+
* @throws {Error} If auth has already been registered by another source
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // In authPlugin registration
|
|
32
|
+
* checkDoubleRegistration(fastify, 'authPlugin');
|
|
33
|
+
*
|
|
34
|
+
* // In adapter plugin registration
|
|
35
|
+
* checkDoubleRegistration(fastify, `adapter:${adapter.name}`);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkDoubleRegistration(fastify: FastifyInstance, source: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Decorates a Fastify instance with auth-related request decorators.
|
|
41
|
+
*
|
|
42
|
+
* This function safely adds `auth` and `user` properties to requests,
|
|
43
|
+
* checking if they already exist (idempotent operation).
|
|
44
|
+
*
|
|
45
|
+
* @param fastify - Fastify server instance to decorate
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* decorateAuth(fastify);
|
|
50
|
+
* // Now all requests will have request.auth and request.user available
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function decorateAuth(fastify: FastifyInstance): void;
|
|
54
|
+
/**
|
|
55
|
+
* Sets the auth context and user on a request.
|
|
56
|
+
*
|
|
57
|
+
* This is a type-safe helper that properly casts the request to include
|
|
58
|
+
* auth properties before setting them.
|
|
59
|
+
*
|
|
60
|
+
* @param request - Fastify request object
|
|
61
|
+
* @param auth - Auth context to set
|
|
62
|
+
* @param user - User to set (optional, defaults to auth.user if NativeAuthContext)
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* setRequestAuth(request, {
|
|
67
|
+
* authMode: 'native',
|
|
68
|
+
* isAuthenticated: true,
|
|
69
|
+
* token: tokenPayload,
|
|
70
|
+
* payload: tokenPayload,
|
|
71
|
+
* }, user);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function setRequestAuth(request: FastifyRequest, auth: AuthContext, user?: User): void;
|
|
75
|
+
/**
|
|
76
|
+
* Gets the current auth context from a request.
|
|
77
|
+
*
|
|
78
|
+
* @param request - Fastify request object
|
|
79
|
+
* @returns The auth context, or undefined if not set
|
|
80
|
+
*/
|
|
81
|
+
export declare function getRequestAuth(request: FastifyRequest): AuthContext | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Gets the current user from a request.
|
|
84
|
+
*
|
|
85
|
+
* @param request - Fastify request object
|
|
86
|
+
* @returns The user, or undefined if not authenticated
|
|
87
|
+
*/
|
|
88
|
+
export declare function getRequestUser(request: FastifyRequest): User | undefined;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared decoration utilities for @veloxts/auth
|
|
3
|
+
*
|
|
4
|
+
* This module provides common functionality for decorating Fastify instances
|
|
5
|
+
* and requests with authentication state, shared between the native auth plugin
|
|
6
|
+
* and external auth adapters.
|
|
7
|
+
*
|
|
8
|
+
* @module auth/decoration
|
|
9
|
+
*/
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Registration Protection
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Symbol used to mark a Fastify instance as having auth already registered.
|
|
15
|
+
*
|
|
16
|
+
* This prevents double-registration of conflicting auth systems (e.g., using
|
|
17
|
+
* both authPlugin and an AuthAdapter on the same server).
|
|
18
|
+
*/
|
|
19
|
+
export const AUTH_REGISTERED = Symbol.for('@veloxts/auth/registered');
|
|
20
|
+
/**
|
|
21
|
+
* Checks for double-registration of auth systems and throws if detected.
|
|
22
|
+
*
|
|
23
|
+
* Call this at the start of both `authPlugin` and `createAuthAdapterPlugin`
|
|
24
|
+
* registration to ensure only one auth system is active.
|
|
25
|
+
*
|
|
26
|
+
* @param fastify - Fastify server instance
|
|
27
|
+
* @param source - Identifier for the auth system being registered (e.g., 'authPlugin', 'adapter:better-auth')
|
|
28
|
+
* @throws {Error} If auth has already been registered by another source
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // In authPlugin registration
|
|
33
|
+
* checkDoubleRegistration(fastify, 'authPlugin');
|
|
34
|
+
*
|
|
35
|
+
* // In adapter plugin registration
|
|
36
|
+
* checkDoubleRegistration(fastify, `adapter:${adapter.name}`);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function checkDoubleRegistration(fastify, source) {
|
|
40
|
+
const decorated = fastify;
|
|
41
|
+
if (decorated[AUTH_REGISTERED]) {
|
|
42
|
+
throw new Error(`Auth already registered by "${decorated[AUTH_REGISTERED]}". ` +
|
|
43
|
+
`Cannot register "${source}". ` +
|
|
44
|
+
`Use either authPlugin OR an AuthAdapter, not both.`);
|
|
45
|
+
}
|
|
46
|
+
decorated[AUTH_REGISTERED] = source;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Decorates a Fastify instance with auth-related request decorators.
|
|
50
|
+
*
|
|
51
|
+
* This function safely adds `auth` and `user` properties to requests,
|
|
52
|
+
* checking if they already exist (idempotent operation).
|
|
53
|
+
*
|
|
54
|
+
* @param fastify - Fastify server instance to decorate
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* decorateAuth(fastify);
|
|
59
|
+
* // Now all requests will have request.auth and request.user available
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function decorateAuth(fastify) {
|
|
63
|
+
if (!fastify.hasRequestDecorator('auth')) {
|
|
64
|
+
fastify.decorateRequest('auth', undefined);
|
|
65
|
+
}
|
|
66
|
+
if (!fastify.hasRequestDecorator('user')) {
|
|
67
|
+
fastify.decorateRequest('user', undefined);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Sets the auth context and user on a request.
|
|
72
|
+
*
|
|
73
|
+
* This is a type-safe helper that properly casts the request to include
|
|
74
|
+
* auth properties before setting them.
|
|
75
|
+
*
|
|
76
|
+
* @param request - Fastify request object
|
|
77
|
+
* @param auth - Auth context to set
|
|
78
|
+
* @param user - User to set (optional, defaults to auth.user if NativeAuthContext)
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* setRequestAuth(request, {
|
|
83
|
+
* authMode: 'native',
|
|
84
|
+
* isAuthenticated: true,
|
|
85
|
+
* token: tokenPayload,
|
|
86
|
+
* payload: tokenPayload,
|
|
87
|
+
* }, user);
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function setRequestAuth(request, auth, user) {
|
|
91
|
+
const decoratedRequest = request;
|
|
92
|
+
decoratedRequest.auth = auth;
|
|
93
|
+
decoratedRequest.user = user ?? (auth.isAuthenticated && 'user' in auth ? auth.user : undefined);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Gets the current auth context from a request.
|
|
97
|
+
*
|
|
98
|
+
* @param request - Fastify request object
|
|
99
|
+
* @returns The auth context, or undefined if not set
|
|
100
|
+
*/
|
|
101
|
+
export function getRequestAuth(request) {
|
|
102
|
+
return request.auth;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Gets the current user from a request.
|
|
106
|
+
*
|
|
107
|
+
* @param request - Fastify request object
|
|
108
|
+
* @returns The user, or undefined if not authenticated
|
|
109
|
+
*/
|
|
110
|
+
export function getRequestUser(request) {
|
|
111
|
+
return request.user;
|
|
112
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,12 +8,9 @@
|
|
|
8
8
|
* @module @veloxts/auth
|
|
9
9
|
*/
|
|
10
10
|
export { AUTH_VERSION } from './plugin.js';
|
|
11
|
-
export type { AuthConfig, AuthContext, AuthMiddlewareOptions, GuardDefinition, GuardFunction, HashConfig, JwtConfig,
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated Use SessionConfig from session.ts for full session management
|
|
14
|
-
*/
|
|
15
|
-
LegacySessionConfig, PolicyAction, PolicyDefinition, RateLimitConfig, TokenPair, TokenPayload, User, } from './types.js';
|
|
11
|
+
export type { AdapterAuthContext, AuthConfig, AuthContext, AuthMiddlewareOptions, BaseAuthContext, GuardDefinition, GuardFunction, HashConfig, JwtConfig, NativeAuthContext, PolicyAction, PolicyDefinition, RateLimitConfig, TokenPair, TokenPayload, User, } from './types.js';
|
|
16
12
|
export { AuthError } from './types.js';
|
|
13
|
+
export { AUTH_REGISTERED, checkDoubleRegistration, decorateAuth, getRequestAuth, getRequestUser, setRequestAuth, } from './decoration.js';
|
|
17
14
|
export type { TokenStore } from './jwt.js';
|
|
18
15
|
export { createInMemoryTokenStore, generateTokenId, isValidTimespan, JwtManager, jwtManager, parseTimeToSeconds, validateTokenExpiration, } from './jwt.js';
|
|
19
16
|
export type { EnhancedTokenStore, EnhancedTokenStoreOptions } from './token-store.js';
|
|
@@ -26,16 +23,18 @@ export { authorize, can, cannot, clearPolicies, createAdminOnlyPolicy, createOwn
|
|
|
26
23
|
export { authMiddleware, clearRateLimitStore, rateLimitMiddleware, } from './middleware.js';
|
|
27
24
|
export type { AuthRateLimitConfig, AuthRateLimiter, AuthRateLimiterConfig } from './rate-limit.js';
|
|
28
25
|
export { authRateLimiter, clearAuthRateLimitStore, createAuthRateLimiter, stopAuthRateLimitCleanup, } from './rate-limit.js';
|
|
29
|
-
export type { AuthPluginOptions, AuthService } from './plugin.js';
|
|
30
|
-
export { authPlugin, defaultAuthPlugin, } from './plugin.js';
|
|
26
|
+
export type { AuthPluginOptions, AuthService, JwtAuthOptions } from './plugin.js';
|
|
27
|
+
export { authPlugin, defaultAuthPlugin, jwtAuth, } from './plugin.js';
|
|
31
28
|
export type { CsrfConfig, CsrfContext, CsrfCookieConfig, CsrfErrorCode, CsrfManager, CsrfMiddlewareOptions, CsrfTokenConfig, CsrfTokenData, CsrfTokenResult, CsrfValidationConfig, } from './csrf.js';
|
|
32
29
|
export { CsrfError, csrfManager, csrfMiddleware, } from './csrf.js';
|
|
33
30
|
export type { Session, SessionAuthContext, SessionConfig, SessionContext, SessionCookieConfig, SessionData, SessionExpirationConfig, SessionManager, SessionMiddlewareOptions, SessionStore, StoredSession, } from './session.js';
|
|
34
31
|
export { inMemorySessionStore, sessionManager, sessionMiddleware, } from './session.js';
|
|
35
|
-
export type {
|
|
32
|
+
export type { AdapterHttpMethod, AdapterMiddlewareContext, AdapterMiddlewareOptions, AdapterRoute, AdapterSession, AdapterSessionResult, AdapterUser, AuthAdapter, AuthAdapterConfig, AuthAdapterErrorCode, AuthAdapterPluginOptions, InferAdapterConfig, } from './adapter.js';
|
|
36
33
|
export { AuthAdapterError, BaseAuthAdapter, createAdapterAuthMiddleware, createAuthAdapterPlugin, defineAuthAdapter, isAuthAdapter, } from './adapter.js';
|
|
37
34
|
export type { BetterAuthAdapterConfig, BetterAuthApi, BetterAuthHandler, BetterAuthInstance, BetterAuthSession, BetterAuthSessionResult, BetterAuthUser, } from './adapters/better-auth.js';
|
|
38
35
|
export { BetterAuthAdapter, createBetterAuthAdapter } from './adapters/better-auth.js';
|
|
36
|
+
export type { JwtAdapterConfig } from './adapters/jwt-adapter.js';
|
|
37
|
+
export { createJwtAdapter, JwtAdapter } from './adapters/jwt-adapter.js';
|
|
39
38
|
export type { PasswordPolicyConfig, PasswordValidationResult, UserInfo, } from './password-policy.js';
|
|
40
39
|
export { checkPasswordBreach, checkPasswordStrength, isCommonPassword, PasswordPolicy, PasswordStrength, passwordPolicy, } from './password-policy.js';
|
|
41
40
|
/**
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
export { AUTH_VERSION } from './plugin.js';
|
|
14
14
|
export { AuthError } from './types.js';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Decoration Utilities
|
|
17
|
+
// ============================================================================
|
|
18
|
+
export { AUTH_REGISTERED, checkDoubleRegistration, decorateAuth, getRequestAuth, getRequestUser, setRequestAuth, } from './decoration.js';
|
|
15
19
|
export { createInMemoryTokenStore, generateTokenId, isValidTimespan, JwtManager, jwtManager, parseTimeToSeconds, validateTokenExpiration, } from './jwt.js';
|
|
16
20
|
export { createEnhancedTokenStore, DEFAULT_ALLOWED_ROLES, parseUserRoles, } from './token-store.js';
|
|
17
21
|
export { authenticatedNarrow, hasRoleNarrow } from './guards-narrowing.js';
|
|
@@ -54,7 +58,7 @@ authRateLimiter,
|
|
|
54
58
|
clearAuthRateLimitStore,
|
|
55
59
|
// Factory
|
|
56
60
|
createAuthRateLimiter, stopAuthRateLimitCleanup, } from './rate-limit.js';
|
|
57
|
-
export { authPlugin, defaultAuthPlugin, } from './plugin.js';
|
|
61
|
+
export { authPlugin, defaultAuthPlugin, jwtAuth, } from './plugin.js';
|
|
58
62
|
export { CsrfError, csrfManager, csrfMiddleware, } from './csrf.js';
|
|
59
63
|
export { inMemorySessionStore, sessionManager, sessionMiddleware, } from './session.js';
|
|
60
64
|
export {
|
|
@@ -67,6 +71,7 @@ createAdapterAuthMiddleware, createAuthAdapterPlugin, defineAuthAdapter,
|
|
|
67
71
|
// Type guard
|
|
68
72
|
isAuthAdapter, } from './adapter.js';
|
|
69
73
|
export { BetterAuthAdapter, createBetterAuthAdapter } from './adapters/better-auth.js';
|
|
74
|
+
export { createJwtAdapter, JwtAdapter } from './adapters/jwt-adapter.js';
|
|
70
75
|
export { checkPasswordBreach, checkPasswordStrength, isCommonPassword, PasswordPolicy, PasswordStrength, passwordPolicy, } from './password-policy.js';
|
|
71
76
|
// ============================================================================
|
|
72
77
|
// Dependency Injection
|
package/dist/middleware.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { BaseContext } from '@veloxts/core';
|
|
6
6
|
import type { MiddlewareFunction } from '@veloxts/router';
|
|
7
7
|
import { JwtManager } from './jwt.js';
|
|
8
|
-
import type { AuthConfig,
|
|
8
|
+
import type { AuthConfig, AuthMiddlewareOptions, GuardDefinition, NativeAuthContext, User } from './types.js';
|
|
9
9
|
/**
|
|
10
10
|
* Creates an authentication middleware for procedures (succinct API)
|
|
11
11
|
*
|
|
@@ -46,15 +46,15 @@ import type { AuthConfig, AuthContext, AuthMiddlewareOptions, GuardDefinition, U
|
|
|
46
46
|
export declare function authMiddleware(config: AuthConfig): {
|
|
47
47
|
middleware: <TInput, TContext extends BaseContext, TOutput>(options?: AuthMiddlewareOptions) => MiddlewareFunction<TInput, TContext, TContext & {
|
|
48
48
|
user?: User;
|
|
49
|
-
auth:
|
|
49
|
+
auth: NativeAuthContext;
|
|
50
50
|
}, TOutput>;
|
|
51
51
|
requireAuth: <TInput, TContext extends BaseContext, TOutput>(guards?: Array<GuardDefinition | string>) => MiddlewareFunction<TInput, TContext, TContext & {
|
|
52
52
|
user: User;
|
|
53
|
-
auth:
|
|
53
|
+
auth: NativeAuthContext;
|
|
54
54
|
}, TOutput>;
|
|
55
55
|
optionalAuth: <TInput, TContext extends BaseContext, TOutput>() => MiddlewareFunction<TInput, TContext, TContext & {
|
|
56
56
|
user?: User;
|
|
57
|
-
auth:
|
|
57
|
+
auth: NativeAuthContext;
|
|
58
58
|
}, TOutput>;
|
|
59
59
|
jwt: JwtManager;
|
|
60
60
|
};
|
package/dist/middleware.js
CHANGED
|
@@ -61,8 +61,10 @@ export function authMiddleware(config) {
|
|
|
61
61
|
if (options.optional) {
|
|
62
62
|
// Optional auth - continue without user
|
|
63
63
|
const authContext = {
|
|
64
|
+
authMode: 'native',
|
|
64
65
|
user: undefined,
|
|
65
66
|
token: undefined,
|
|
67
|
+
payload: undefined,
|
|
66
68
|
isAuthenticated: false,
|
|
67
69
|
};
|
|
68
70
|
return next({
|
|
@@ -85,8 +87,10 @@ export function authMiddleware(config) {
|
|
|
85
87
|
if (options.optional) {
|
|
86
88
|
// Invalid token with optional auth - continue without user
|
|
87
89
|
const authContext = {
|
|
90
|
+
authMode: 'native',
|
|
88
91
|
user: undefined,
|
|
89
92
|
token: undefined,
|
|
93
|
+
payload: undefined,
|
|
90
94
|
isAuthenticated: false,
|
|
91
95
|
};
|
|
92
96
|
return next({
|
|
@@ -123,8 +127,10 @@ export function authMiddleware(config) {
|
|
|
123
127
|
}
|
|
124
128
|
// Create auth context
|
|
125
129
|
const authContext = {
|
|
130
|
+
authMode: 'native',
|
|
126
131
|
user: user ?? undefined,
|
|
127
|
-
token
|
|
132
|
+
token,
|
|
133
|
+
payload,
|
|
128
134
|
isAuthenticated: !!user,
|
|
129
135
|
};
|
|
130
136
|
// Build extended context
|