@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/dist/plugin.d.ts CHANGED
@@ -1,13 +1,18 @@
1
1
  /**
2
2
  * VeloxTS Auth Plugin
3
- * Fastify plugin that integrates authentication with VeloxApp
3
+ *
4
+ * Unified authentication using the adapter pattern internally.
5
+ * This plugin provides a convenient API while using JwtAdapter under the hood.
6
+ *
4
7
  * @module auth/plugin
5
8
  */
6
9
  import type { Container, VeloxPlugin } from '@veloxts/core';
10
+ import type { AuthAdapterPluginOptions } from './adapter.js';
11
+ import type { JwtAdapterConfig } from './adapters/jwt-adapter.js';
7
12
  import { PasswordHasher } from './hash.js';
8
- import { JwtManager } from './jwt.js';
13
+ import type { JwtManager, TokenStore } from './jwt.js';
9
14
  import { authMiddleware } from './middleware.js';
10
- import type { AuthConfig, AuthContext, TokenPair, User } from './types.js';
15
+ import type { AdapterAuthContext, AuthConfig, TokenPair, User } from './types.js';
11
16
  /** Auth package version */
12
17
  export declare const AUTH_VERSION: string;
13
18
  /**
@@ -59,6 +64,10 @@ export interface AuthService {
59
64
  * Password hasher for secure password storage
60
65
  */
61
66
  hasher: PasswordHasher;
67
+ /**
68
+ * Token store for revocation (if configured)
69
+ */
70
+ tokenStore?: TokenStore;
62
71
  /**
63
72
  * Creates a token pair for a user
64
73
  */
@@ -66,7 +75,7 @@ export interface AuthService {
66
75
  /**
67
76
  * Verifies an access token and returns the auth context
68
77
  */
69
- verifyToken(token: string): AuthContext;
78
+ verifyToken(token: string): AdapterAuthContext;
70
79
  /**
71
80
  * Refreshes tokens using a refresh token
72
81
  */
@@ -76,6 +85,7 @@ export interface AuthService {
76
85
  */
77
86
  middleware: ReturnType<typeof authMiddleware>;
78
87
  }
88
+ import type { AuthContext } from './types.js';
79
89
  declare module 'fastify' {
80
90
  interface FastifyInstance {
81
91
  auth: AuthService;
@@ -86,7 +96,10 @@ declare module 'fastify' {
86
96
  }
87
97
  }
88
98
  /**
89
- * Creates the VeloxTS auth plugin (succinct API)
99
+ * Creates the VeloxTS auth plugin
100
+ *
101
+ * **Internally uses the JwtAdapter** for unified architecture.
102
+ * All authentication in VeloxTS uses the adapter pattern.
90
103
  *
91
104
  * This plugin provides:
92
105
  * - JWT token management (access + refresh tokens)
@@ -142,3 +155,55 @@ export declare function authPlugin(options: AuthPluginOptions): VeloxPlugin<Auth
142
155
  * ```
143
156
  */
144
157
  export declare function defaultAuthPlugin(): VeloxPlugin<AuthPluginOptions>;
158
+ /**
159
+ * Options for jwtAuth convenience function
160
+ *
161
+ * Omits the 'name' field since it's auto-set to 'jwt'.
162
+ */
163
+ export type JwtAuthOptions = Omit<JwtAdapterConfig, 'name'>;
164
+ /**
165
+ * Creates JWT auth using the adapter pattern directly
166
+ *
167
+ * This is an alternative to `authPlugin` that gives you more control over
168
+ * adapter-specific features like built-in routes and route prefixes.
169
+ *
170
+ * **Note:** Both `authPlugin` and `jwtAuth` now use the adapter pattern internally.
171
+ * Choose based on your needs:
172
+ *
173
+ * **Use `authPlugin` when:**
174
+ * - You need the `authMiddleware` factory for fine-grained procedure control
175
+ * - You're using DI container integration
176
+ * - You want the familiar VeloxTS auth API (`fastify.auth.createTokens()`, etc.)
177
+ *
178
+ * **Use `jwtAuth` when:**
179
+ * - You want built-in `/api/auth/refresh` and `/api/auth/logout` routes
180
+ * - You're building a pure adapter-based setup
181
+ * - You want direct access to adapter features
182
+ *
183
+ * @param options - JWT adapter configuration
184
+ * @returns VeloxPlugin ready for registration
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * import { jwtAuth } from '@veloxts/auth';
189
+ *
190
+ * // With built-in routes
191
+ * app.use(jwtAuth({
192
+ * jwt: {
193
+ * secret: process.env.JWT_SECRET!,
194
+ * accessTokenExpiry: '15m',
195
+ * refreshTokenExpiry: '7d',
196
+ * },
197
+ * userLoader: async (userId) => {
198
+ * return db.user.findUnique({ where: { id: userId } });
199
+ * },
200
+ * enableRoutes: true, // Mount /api/auth/refresh and /api/auth/logout
201
+ * routePrefix: '/api/auth',
202
+ * }));
203
+ *
204
+ * // Access JWT utilities via fastify
205
+ * const tokens = fastify.jwtManager!.createTokenPair(user);
206
+ * await fastify.tokenStore!.revoke(tokenId);
207
+ * ```
208
+ */
209
+ export declare function jwtAuth(options: JwtAuthOptions): VeloxPlugin<AuthAdapterPluginOptions<JwtAdapterConfig>>;
package/dist/plugin.js CHANGED
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * VeloxTS Auth Plugin
3
- * Fastify plugin that integrates authentication with VeloxApp
3
+ *
4
+ * Unified authentication using the adapter pattern internally.
5
+ * This plugin provides a convenient API while using JwtAdapter under the hood.
6
+ *
4
7
  * @module auth/plugin
5
8
  */
6
9
  import { createRequire } from 'node:module';
10
+ import { createAuthAdapterPlugin } from './adapter.js';
11
+ import { createJwtAdapter } from './adapters/jwt-adapter.js';
12
+ import { checkDoubleRegistration, decorateAuth } from './decoration.js';
7
13
  import { PasswordHasher } from './hash.js';
8
- import { JwtManager } from './jwt.js';
9
14
  import { authMiddleware } from './middleware.js';
10
15
  import { registerAuthProviders } from './providers.js';
11
16
  import { AUTH_SERVICE } from './tokens.js';
@@ -18,7 +23,26 @@ export const AUTH_VERSION = packageJson.version ?? '0.0.0-unknown';
18
23
  // Auth Plugin
19
24
  // ============================================================================
20
25
  /**
21
- * Creates the VeloxTS auth plugin (succinct API)
26
+ * Wraps isTokenRevoked callback as a TokenStore interface
27
+ * @internal
28
+ */
29
+ function createCallbackTokenStore(isTokenRevoked) {
30
+ return {
31
+ revoke: () => {
32
+ // No-op: callback-based stores don't support revocation
33
+ // Users must implement their own revocation mechanism
34
+ },
35
+ isRevoked: isTokenRevoked,
36
+ clear: () => {
37
+ // No-op
38
+ },
39
+ };
40
+ }
41
+ /**
42
+ * Creates the VeloxTS auth plugin
43
+ *
44
+ * **Internally uses the JwtAdapter** for unified architecture.
45
+ * All authentication in VeloxTS uses the adapter pattern.
22
46
  *
23
47
  * This plugin provides:
24
48
  * - JWT token management (access + refresh tokens)
@@ -60,43 +84,103 @@ export function authPlugin(options) {
60
84
  return {
61
85
  name: '@veloxts/auth',
62
86
  version: AUTH_VERSION,
63
- // No explicit dependencies - works with any Fastify instance
64
- // The plugin decorates Fastify with auth functionality
65
87
  async register(server, _opts) {
66
88
  const config = { ...options, ..._opts };
67
89
  const { debug = false, container } = config;
90
+ // Prevent double-registration of auth systems
91
+ checkDoubleRegistration(server, 'authPlugin');
68
92
  if (debug) {
69
- server.log.info('Registering @veloxts/auth plugin');
93
+ server.log.info('Registering @veloxts/auth plugin (adapter-based)');
70
94
  }
71
- let authService;
95
+ // DI-enabled path: Use container for service resolution
72
96
  if (container) {
73
- // DI-enabled path: Register providers and resolve from container
74
97
  if (debug) {
75
98
  server.log.info('Using DI container for auth services');
76
99
  }
77
100
  registerAuthProviders(container, config);
78
- authService = container.resolve(AUTH_SERVICE);
101
+ const authService = container.resolve(AUTH_SERVICE);
102
+ server.decorate('auth', authService);
103
+ // Still need to register the adapter plugin for session loading
104
+ const { adapter, config: adapterConfig } = createJwtAdapter({
105
+ jwt: config.jwt,
106
+ userLoader: config.userLoader,
107
+ tokenStore: config.isTokenRevoked
108
+ ? createCallbackTokenStore(config.isTokenRevoked)
109
+ : undefined,
110
+ enableRoutes: false, // Don't mount routes when using authPlugin
111
+ debug,
112
+ });
113
+ // Initialize the adapter for session loading
114
+ await adapter.initialize(server, adapterConfig);
115
+ // Decorate requests with auth context
116
+ decorateAuth(server);
117
+ // Add preHandler hook for session loading
118
+ server.addHook('preHandler', async (request) => {
119
+ if (config.autoExtract === false)
120
+ return;
121
+ const session = await adapter.getSession(request);
122
+ if (session) {
123
+ const user = {
124
+ id: session.user.id,
125
+ email: session.user.email,
126
+ ...(session.user.emailVerified !== undefined && {
127
+ emailVerified: session.user.emailVerified,
128
+ }),
129
+ ...session.user.providerData,
130
+ };
131
+ const authContext = {
132
+ authMode: 'adapter',
133
+ isAuthenticated: true,
134
+ user,
135
+ providerId: 'jwt',
136
+ session: session.session.providerData,
137
+ };
138
+ request.auth = authContext;
139
+ request.user = user;
140
+ }
141
+ });
79
142
  }
80
143
  else {
81
- // Legacy path: Direct instantiation (backward compatible)
82
- const jwt = new JwtManager(config.jwt);
144
+ // Adapter-based path: Use JwtAdapter directly
145
+ // Convert isTokenRevoked callback to TokenStore if provided
146
+ const tokenStore = config.isTokenRevoked
147
+ ? createCallbackTokenStore(config.isTokenRevoked)
148
+ : undefined;
149
+ // Create the JWT adapter
150
+ const { adapter, config: adapterConfig } = createJwtAdapter({
151
+ jwt: config.jwt,
152
+ userLoader: config.userLoader,
153
+ tokenStore,
154
+ enableRoutes: false, // authPlugin manages its own API
155
+ debug,
156
+ });
157
+ // Initialize adapter
158
+ await adapter.initialize(server, adapterConfig);
159
+ // Decorate requests with auth context
160
+ decorateAuth(server);
161
+ // Get JWT manager from adapter
162
+ const jwt = adapter.getJwtManager();
83
163
  const hasher = new PasswordHasher(config.hash);
84
164
  const authMw = authMiddleware(config);
85
- authService = {
165
+ // Build AuthService from adapter
166
+ const authService = {
86
167
  jwt,
87
168
  hasher,
169
+ tokenStore: adapter.getTokenStore(),
88
170
  createTokens(user, additionalClaims) {
89
171
  return jwt.createTokenPair(user, additionalClaims);
90
172
  },
91
173
  verifyToken(token) {
92
174
  const payload = jwt.verifyToken(token);
93
175
  return {
176
+ authMode: 'adapter',
94
177
  user: {
95
178
  id: payload.sub,
96
179
  email: payload.email,
97
180
  },
98
- token: payload,
99
181
  isAuthenticated: true,
182
+ providerId: 'jwt',
183
+ session: { token, payload },
100
184
  };
101
185
  },
102
186
  refreshTokens(refreshToken) {
@@ -107,56 +191,33 @@ export function authPlugin(options) {
107
191
  },
108
192
  middleware: authMw,
109
193
  };
110
- }
111
- // Decorate server with auth service
112
- server.decorate('auth', authService);
113
- // Decorate requests with auth context (undefined initial value)
114
- server.decorateRequest('auth', undefined);
115
- server.decorateRequest('user', undefined);
116
- // Add preHandler hook to extract auth from headers (optional)
117
- if (config.autoExtract !== false) {
118
- server.addHook('preHandler', async (request) => {
119
- const authHeader = request.headers.authorization;
120
- const token = authService.jwt.extractFromHeader(authHeader);
121
- if (token) {
122
- try {
123
- const payload = authService.jwt.verifyToken(token);
124
- // Check if token is revoked
125
- if (config.isTokenRevoked && payload.jti) {
126
- const revoked = await config.isTokenRevoked(payload.jti);
127
- if (revoked) {
128
- // Token revoked - don't set auth context
129
- return;
130
- }
131
- }
132
- // Load user if loader provided
133
- let user = null;
134
- if (config.userLoader) {
135
- user = await config.userLoader(payload.sub);
136
- }
137
- else {
138
- user = {
139
- id: payload.sub,
140
- email: payload.email,
141
- };
142
- }
143
- if (user) {
144
- request.auth = {
145
- user,
146
- token: payload,
147
- isAuthenticated: true,
148
- };
149
- request.user = user;
150
- }
151
- }
152
- catch {
153
- // Invalid token - silently ignore (optional auth)
154
- if (debug) {
155
- server.log.debug('Invalid auth token in request');
156
- }
194
+ // Decorate server with auth service
195
+ server.decorate('auth', authService);
196
+ // Add preHandler hook for session loading (using adapter)
197
+ if (config.autoExtract !== false) {
198
+ server.addHook('preHandler', async (request) => {
199
+ const session = await adapter.getSession(request);
200
+ if (session) {
201
+ const user = {
202
+ id: session.user.id,
203
+ email: session.user.email,
204
+ ...(session.user.emailVerified !== undefined && {
205
+ emailVerified: session.user.emailVerified,
206
+ }),
207
+ ...session.user.providerData,
208
+ };
209
+ const authContext = {
210
+ authMode: 'adapter',
211
+ isAuthenticated: true,
212
+ user,
213
+ providerId: 'jwt',
214
+ session: session.session.providerData,
215
+ };
216
+ request.auth = authContext;
217
+ request.user = user;
157
218
  }
158
- }
159
- });
219
+ });
220
+ }
160
221
  }
161
222
  // Add shutdown hook for cleanup
162
223
  server.addHook('onClose', async () => {
@@ -196,3 +257,52 @@ export function defaultAuthPlugin() {
196
257
  jwt: { secret },
197
258
  });
198
259
  }
260
+ /**
261
+ * Creates JWT auth using the adapter pattern directly
262
+ *
263
+ * This is an alternative to `authPlugin` that gives you more control over
264
+ * adapter-specific features like built-in routes and route prefixes.
265
+ *
266
+ * **Note:** Both `authPlugin` and `jwtAuth` now use the adapter pattern internally.
267
+ * Choose based on your needs:
268
+ *
269
+ * **Use `authPlugin` when:**
270
+ * - You need the `authMiddleware` factory for fine-grained procedure control
271
+ * - You're using DI container integration
272
+ * - You want the familiar VeloxTS auth API (`fastify.auth.createTokens()`, etc.)
273
+ *
274
+ * **Use `jwtAuth` when:**
275
+ * - You want built-in `/api/auth/refresh` and `/api/auth/logout` routes
276
+ * - You're building a pure adapter-based setup
277
+ * - You want direct access to adapter features
278
+ *
279
+ * @param options - JWT adapter configuration
280
+ * @returns VeloxPlugin ready for registration
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * import { jwtAuth } from '@veloxts/auth';
285
+ *
286
+ * // With built-in routes
287
+ * app.use(jwtAuth({
288
+ * jwt: {
289
+ * secret: process.env.JWT_SECRET!,
290
+ * accessTokenExpiry: '15m',
291
+ * refreshTokenExpiry: '7d',
292
+ * },
293
+ * userLoader: async (userId) => {
294
+ * return db.user.findUnique({ where: { id: userId } });
295
+ * },
296
+ * enableRoutes: true, // Mount /api/auth/refresh and /api/auth/logout
297
+ * routePrefix: '/api/auth',
298
+ * }));
299
+ *
300
+ * // Access JWT utilities via fastify
301
+ * const tokens = fastify.jwtManager!.createTokenPair(user);
302
+ * await fastify.tokenStore!.revoke(tokenId);
303
+ * ```
304
+ */
305
+ export function jwtAuth(options) {
306
+ const { adapter, config } = createJwtAdapter(options);
307
+ return createAuthAdapterPlugin({ adapter, config });
308
+ }
package/dist/providers.js CHANGED
@@ -109,12 +109,14 @@ export function authServiceProvider() {
109
109
  verifyToken(token) {
110
110
  const payload = jwt.verifyToken(token);
111
111
  return {
112
+ authMode: 'adapter',
112
113
  user: {
113
114
  id: payload.sub,
114
115
  email: payload.email,
115
116
  },
116
- token: payload,
117
117
  isAuthenticated: true,
118
+ providerId: 'jwt',
119
+ session: { token, payload },
118
120
  };
119
121
  },
120
122
  refreshTokens(refreshToken) {
package/dist/types.d.ts CHANGED
@@ -24,25 +24,36 @@ export declare class AuthError extends Error {
24
24
  /**
25
25
  * Base user interface for authenticated requests
26
26
  *
27
- * Applications should extend this via declaration merging:
27
+ * Applications should extend this via declaration merging to add
28
+ * custom properties without using index signatures:
29
+ *
28
30
  * @example
29
31
  * ```typescript
30
32
  * declare module '@veloxts/auth' {
31
33
  * interface User {
32
- * roles: ('admin' | 'user')[];
33
- * permissions: string[];
34
+ * name?: string;
35
+ * avatarUrl?: string;
36
+ * tenantId?: string;
34
37
  * }
35
38
  * }
36
39
  * ```
40
+ *
41
+ * This approach provides:
42
+ * - Full type safety (no implicit `unknown` properties)
43
+ * - Better IDE autocomplete
44
+ * - Compile-time errors for typos
37
45
  */
38
46
  export interface User {
47
+ /** Unique user identifier */
39
48
  id: string;
49
+ /** User email address */
40
50
  email: string;
51
+ /** Whether the user's email has been verified */
52
+ emailVerified?: boolean;
41
53
  /** User roles for authorization */
42
54
  roles?: string[];
43
55
  /** User permissions for fine-grained access control */
44
56
  permissions?: string[];
45
- [key: string]: unknown;
46
57
  }
47
58
  /**
48
59
  * Payload stored in JWT tokens
@@ -120,25 +131,6 @@ export interface HashConfig {
120
131
  /** argon2 parallelism (default: 4) */
121
132
  argon2Parallelism?: number;
122
133
  }
123
- /**
124
- * Legacy session cookie configuration (used by AuthConfig)
125
- *
126
- * @deprecated Use SessionConfig from session.ts for full session management
127
- */
128
- export interface LegacySessionConfig {
129
- /** Cookie name (default: 'velox.session') */
130
- cookieName?: string;
131
- /** Session expiration in seconds (default: 86400 = 24h) */
132
- maxAge?: number;
133
- /** Cookie path (default: '/') */
134
- path?: string;
135
- /** HTTP only flag (default: true) */
136
- httpOnly?: boolean;
137
- /** Secure flag - use HTTPS only (default: true in production) */
138
- secure?: boolean;
139
- /** SameSite policy (default: 'lax') */
140
- sameSite?: 'strict' | 'lax' | 'none';
141
- }
142
134
  /**
143
135
  * Rate limiting configuration
144
136
  */
@@ -162,11 +154,6 @@ export interface AuthConfig {
162
154
  jwt: JwtConfig;
163
155
  /** Password hashing configuration */
164
156
  hash?: HashConfig;
165
- /**
166
- * Legacy session cookie configuration
167
- * @deprecated Use createSessionMiddleware from session.ts for full session management
168
- */
169
- session?: LegacySessionConfig;
170
157
  /** Rate limiting configuration */
171
158
  rateLimit?: RateLimitConfig;
172
159
  /**
@@ -177,6 +164,7 @@ export interface AuthConfig {
177
164
  /**
178
165
  * Token blacklist checker - check if token is revoked
179
166
  * Called on every authenticated request
167
+ * @deprecated Use `tokenStore` with TokenStore interface instead
180
168
  */
181
169
  isTokenRevoked?: (tokenId: string) => Promise<boolean>;
182
170
  /**
@@ -234,16 +222,65 @@ export interface AuthMiddlewareOptions {
234
222
  guards?: Array<GuardDefinition | string>;
235
223
  }
236
224
  /**
237
- * Authenticated request context extension
225
+ * Base auth context shared by all auth modes
226
+ */
227
+ export interface BaseAuthContext {
228
+ /** Whether the request is authenticated */
229
+ isAuthenticated: boolean;
230
+ }
231
+ /**
232
+ * Auth context for native JWT authentication (authPlugin)
233
+ *
234
+ * This context is set when using the built-in authPlugin with JWT tokens.
235
+ * Provides access to the decoded token payload.
238
236
  */
239
- export interface AuthContext {
237
+ export interface NativeAuthContext extends BaseAuthContext {
238
+ /** Discriminant for native JWT auth mode */
239
+ authMode: 'native';
240
240
  /** Authenticated user (undefined if optional auth and no token) */
241
241
  user?: User;
242
+ /** Raw JWT token string (if extracted from request) */
243
+ token?: string;
242
244
  /** Decoded token payload */
243
- token?: TokenPayload;
244
- /** Check if user is authenticated */
245
- isAuthenticated: boolean;
245
+ payload?: TokenPayload;
246
246
  }
247
+ /**
248
+ * Auth context for external authentication adapters
249
+ *
250
+ * This context is set when using an AuthAdapter (BetterAuth, Clerk, Auth0, etc.).
251
+ * Provides access to the provider's session data.
252
+ */
253
+ export interface AdapterAuthContext extends BaseAuthContext {
254
+ /** Discriminant for adapter auth mode */
255
+ authMode: 'adapter';
256
+ /** Authenticated user (undefined if not authenticated) */
257
+ user?: User;
258
+ /** Provider identifier (e.g., 'better-auth', 'clerk', 'auth0') */
259
+ providerId: string;
260
+ /** Provider-specific session data */
261
+ session?: unknown;
262
+ }
263
+ /**
264
+ * Authenticated request context extension
265
+ *
266
+ * This is a discriminated union based on `authMode`:
267
+ * - `'native'`: Built-in JWT authentication via authPlugin
268
+ * - `'adapter'`: External provider via AuthAdapter
269
+ *
270
+ * Use the `authMode` discriminant for type-safe access to mode-specific properties:
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * if (ctx.auth?.authMode === 'native') {
275
+ * // Access JWT-specific properties
276
+ * console.log(ctx.auth.payload?.sub);
277
+ * } else if (ctx.auth?.authMode === 'adapter') {
278
+ * // Access adapter-specific properties
279
+ * console.log(ctx.auth.providerId);
280
+ * }
281
+ * ```
282
+ */
283
+ export type AuthContext = NativeAuthContext | AdapterAuthContext;
247
284
  declare module '@veloxts/core' {
248
285
  interface BaseContext {
249
286
  /** Auth context - available when auth middleware is used */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/auth",
3
- "version": "0.6.69",
3
+ "version": "0.6.71",
4
4
  "description": "Authentication and authorization system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -57,8 +57,8 @@
57
57
  "dependencies": {
58
58
  "@fastify/cookie": "11.0.2",
59
59
  "fastify": "5.6.2",
60
- "@veloxts/core": "0.6.69",
61
- "@veloxts/router": "0.6.69"
60
+ "@veloxts/core": "0.6.71",
61
+ "@veloxts/router": "0.6.71"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "argon2": ">=0.30.0",
@@ -82,8 +82,8 @@
82
82
  "fastify-plugin": "5.1.0",
83
83
  "typescript": "5.9.3",
84
84
  "vitest": "4.0.16",
85
- "@veloxts/testing": "0.6.69",
86
- "@veloxts/validation": "0.6.69"
85
+ "@veloxts/testing": "0.6.71",
86
+ "@veloxts/validation": "0.6.71"
87
87
  },
88
88
  "keywords": [
89
89
  "velox",