@veloxts/auth 0.6.68 → 0.6.70

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 CHANGED
@@ -1,5 +1,92 @@
1
1
  # @veloxts/auth
2
2
 
3
+ ## 0.6.70
4
+
5
+ ### Patch Changes
6
+
7
+ - ### feat(auth): Unified Adapter-Only Architecture
8
+
9
+ **New Features:**
10
+
11
+ - Add `JwtAdapter` implementing the `AuthAdapter` interface for unified JWT authentication
12
+ - Add `jwtAuth()` convenience function for direct adapter usage with optional built-in routes (`/api/auth/refresh`, `/api/auth/logout`)
13
+ - Add `AuthContext` discriminated union (`NativeAuthContext | AdapterAuthContext`) for type-safe auth mode handling
14
+ - Add double-registration protection to prevent conflicting auth system setups
15
+ - Add shared decoration utilities (`decorateAuth`, `setRequestAuth`, `checkDoubleRegistration`)
16
+
17
+ **Architecture Changes:**
18
+
19
+ - `authPlugin` now uses `JwtAdapter` internally - all authentication flows through the adapter pattern
20
+ - Single code path for authentication (no more dual native/adapter modes)
21
+ - `authContext.authMode` is now always `'adapter'` with `providerId='jwt'` when using `authPlugin`
22
+
23
+ **Breaking Changes:**
24
+
25
+ - Remove deprecated `LegacySessionConfig` interface (use `sessionMiddleware` instead)
26
+ - Remove deprecated `session` field from `AuthConfig`
27
+ - `User` interface no longer has index signature (extend via declaration merging)
28
+
29
+ **Type Safety Improvements:**
30
+
31
+ - `AuthContext` discriminated union enables exhaustive type narrowing based on `authMode`
32
+ - Export `NativeAuthContext` and `AdapterAuthContext` types for explicit typing
33
+
34
+ **Migration:**
35
+
36
+ - Existing `authPlugin` usage remains backward-compatible
37
+ - If checking `authContext.token`, use `authContext.session` instead (token stored in session for adapter mode)
38
+
39
+ - Updated dependencies
40
+ - @veloxts/core@0.6.70
41
+ - @veloxts/router@0.6.70
42
+
43
+ ## 0.6.69
44
+
45
+ ### Patch Changes
46
+
47
+ - implement user feedback improvements across packages
48
+
49
+ ## Summary
50
+
51
+ Addresses 9 user feedback items to improve DX, reduce boilerplate, and eliminate template duplications.
52
+
53
+ ### Phase 1: Validation Helpers (`@veloxts/validation`)
54
+
55
+ - Add `prismaDecimal()`, `prismaDecimalNullable()`, `prismaDecimalOptional()` for Prisma Decimal → number conversion
56
+ - Add `dateToIso`, `dateToIsoNullable`, `dateToIsoOptional` aliases for consistency
57
+
58
+ ### Phase 2: Template Deduplication (`@veloxts/auth`)
59
+
60
+ - Export `createEnhancedTokenStore()` with token revocation and refresh token reuse detection
61
+ - Export `parseUserRoles()` and `DEFAULT_ALLOWED_ROLES`
62
+ - Fix memory leak: track pending timeouts for proper cleanup on `destroy()`
63
+ - Update templates to import from `@veloxts/auth` instead of duplicating code
64
+ - Fix jwtManager singleton pattern in templates
65
+
66
+ ### Phase 3: Router Helpers (`@veloxts/router`)
67
+
68
+ - Add `createRouter()` returning `{ collections, router }` for DRY setup
69
+ - Add `toRouter()` for router-only use cases
70
+ - Update all router templates to use `createRouter()`
71
+
72
+ ### Phase 4: Guard Type Narrowing - Experimental (`@veloxts/auth`, `@veloxts/router`)
73
+
74
+ - Add `NarrowingGuard` interface with phantom `_narrows` type
75
+ - Add `authenticatedNarrow` and `hasRoleNarrow()` guards
76
+ - Add `guardNarrow()` method to `ProcedureBuilder` for context narrowing
77
+ - Enables `ctx.user` to be non-null after guard passes
78
+
79
+ ### Phase 5: Documentation (`@veloxts/router`)
80
+
81
+ - Document `.rest()` override patterns
82
+ - Document `createRouter()` helper usage
83
+ - Document `guardNarrow()` experimental API
84
+ - Add schema browser-safety patterns for RSC apps
85
+
86
+ - Updated dependencies
87
+ - @veloxts/core@0.6.69
88
+ - @veloxts/router@0.6.69
89
+
3
90
  ## 0.6.68
4
91
 
5
92
  ### Patch Changes
package/dist/adapter.d.ts CHANGED
@@ -551,9 +551,15 @@ export interface AdapterMiddlewareOptions {
551
551
  optional?: boolean;
552
552
  }
553
553
  /**
554
- * Context extension for adapter-based authentication
554
+ * Context extension for adapter-based authentication middleware
555
+ *
556
+ * This interface extends the procedure context with auth-related properties
557
+ * when using `createAdapterAuthMiddleware()`.
558
+ *
559
+ * Note: This is different from `AdapterAuthContext` in types.ts, which represents
560
+ * the discriminated union variant for adapter-based authentication on requests.
555
561
  */
556
- export interface AdapterAuthContext {
562
+ export interface AdapterMiddlewareContext {
557
563
  /** Authenticated user (undefined if optional and not authenticated) */
558
564
  user?: User;
559
565
  /** Whether the request is authenticated */
@@ -590,7 +596,7 @@ export interface AdapterAuthContext {
590
596
  * ```
591
597
  */
592
598
  export declare function createAdapterAuthMiddleware(): {
593
- middleware: <TInput, TContext extends BaseContext, TOutput>(options?: AdapterMiddlewareOptions) => MiddlewareFunction<TInput, TContext, TContext & AdapterAuthContext, TOutput>;
599
+ middleware: <TInput, TContext extends BaseContext, TOutput>(options?: AdapterMiddlewareOptions) => MiddlewareFunction<TInput, TContext, TContext & AdapterMiddlewareContext, TOutput>;
594
600
  requireAuth: <TInput, TContext extends BaseContext, TOutput>() => MiddlewareFunction<TInput, TContext, TContext & {
595
601
  user: User;
596
602
  isAuthenticated: true;
@@ -699,7 +705,7 @@ export declare abstract class BaseAuthAdapter<TConfig extends AuthAdapterConfig
699
705
  */
700
706
  protected debug(message: string): void;
701
707
  /**
702
- * Log an info message
708
+ * Log an info message (if debug is enabled)
703
709
  */
704
710
  protected info(message: string): void;
705
711
  /**
package/dist/adapter.js CHANGED
@@ -26,6 +26,7 @@
26
26
  * app.use(authPlugin);
27
27
  * ```
28
28
  */
29
+ import { checkDoubleRegistration, decorateAuth, setRequestAuth } from './decoration.js';
29
30
  import { AuthError } from './types.js';
30
31
  /**
31
32
  * Authentication adapter error
@@ -216,6 +217,8 @@ export function createAuthAdapterPlugin(options) {
216
217
  dependencies: ['@veloxts/core'],
217
218
  async register(server, _opts) {
218
219
  const mergedConfig = { ...config, ..._opts.config };
220
+ // Prevent double-registration of auth systems
221
+ checkDoubleRegistration(server, `adapter:${adapter.name}`);
219
222
  if (debug) {
220
223
  server.log.info(`Registering auth adapter: ${adapter.name}`);
221
224
  }
@@ -232,8 +235,7 @@ export function createAuthAdapterPlugin(options) {
232
235
  throw adapterError;
233
236
  }
234
237
  // Decorate requests with auth context
235
- server.decorateRequest('auth', undefined);
236
- server.decorateRequest('user', undefined);
238
+ decorateAuth(server);
237
239
  // Mount adapter routes
238
240
  const routes = adapter.getRoutes();
239
241
  for (const route of routes) {
@@ -313,15 +315,15 @@ export function createAuthAdapterPlugin(options) {
313
315
  catch (error) {
314
316
  throw new AuthAdapterError('Failed to transform user', 500, 'ADAPTER_USER_TRANSFORM_ERROR', error instanceof Error ? error : undefined);
315
317
  }
316
- // Set auth context on request
318
+ // Set auth context on request using AdapterAuthContext
317
319
  const authContext = {
320
+ authMode: 'adapter',
318
321
  user,
319
322
  isAuthenticated: true,
320
- // Token payload is not available from adapters
321
- // (they use sessions, not JWTs internally)
323
+ providerId: adapter.name,
324
+ session: sessionResult.session.providerData,
322
325
  };
323
- request.auth = authContext;
324
- request.user = user;
326
+ setRequestAuth(request, authContext, user);
325
327
  }
326
328
  }
327
329
  catch (error) {
@@ -404,9 +406,13 @@ export function createAdapterAuthMiddleware() {
404
406
  if (!user || !auth?.isAuthenticated) {
405
407
  if (options.optional) {
406
408
  // Optional auth - continue without user
409
+ // Create a minimal adapter auth context for unauthenticated state
407
410
  const authContext = {
411
+ authMode: 'adapter',
408
412
  user: undefined,
409
413
  isAuthenticated: false,
414
+ providerId: 'unknown',
415
+ session: undefined,
410
416
  };
411
417
  return next({
412
418
  ctx: {
@@ -559,10 +565,10 @@ export class BaseAuthAdapter {
559
565
  }
560
566
  }
561
567
  /**
562
- * Log an info message
568
+ * Log an info message (if debug is enabled)
563
569
  */
564
570
  info(message) {
565
- if (this.fastify) {
571
+ if (this.config?.debug && this.fastify) {
566
572
  this.fastify.log.info(`[${this.name}] ${message}`);
567
573
  }
568
574
  }
@@ -1,13 +1,31 @@
1
1
  /**
2
- * Auth Adapters - External authentication provider integrations
2
+ * Auth Adapters - Authentication provider integrations
3
3
  *
4
- * This module exports adapters for integrating external authentication
5
- * providers with VeloxTS. Each adapter implements the AuthAdapter interface
6
- * and can be used with createAuthAdapterPlugin.
4
+ * This module exports adapters for integrating authentication providers
5
+ * with VeloxTS. Each adapter implements the AuthAdapter interface and
6
+ * can be used with createAuthAdapterPlugin.
7
+ *
8
+ * Available adapters:
9
+ * - **JwtAdapter** - Built-in JWT authentication using the adapter pattern
10
+ * - **BetterAuthAdapter** - Integration with BetterAuth library
7
11
  *
8
12
  * @module auth/adapters
9
13
  *
10
- * @example
14
+ * @example JWT Adapter
15
+ * ```typescript
16
+ * import { createJwtAdapter } from '@veloxts/auth/adapters';
17
+ * import { createAuthAdapterPlugin } from '@veloxts/auth';
18
+ *
19
+ * const { adapter, config } = createJwtAdapter({
20
+ * jwt: { secret: process.env.JWT_SECRET! },
21
+ * userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
22
+ * });
23
+ *
24
+ * const plugin = createAuthAdapterPlugin({ adapter, config });
25
+ * app.use(plugin);
26
+ * ```
27
+ *
28
+ * @example BetterAuth Adapter
11
29
  * ```typescript
12
30
  * import { createBetterAuthAdapter } from '@veloxts/auth/adapters';
13
31
  * import { createAuthAdapterPlugin } from '@veloxts/auth';
@@ -23,5 +41,7 @@
23
41
  * });
24
42
  * ```
25
43
  */
44
+ export type { JwtAdapterConfig } from './jwt-adapter.js';
45
+ export { AuthAdapterError, createJwtAdapter, JwtAdapter } from './jwt-adapter.js';
26
46
  export type { BetterAuthAdapterConfig, BetterAuthApi, BetterAuthHandler, BetterAuthInstance, BetterAuthSession, BetterAuthSessionResult, BetterAuthUser, } from './better-auth.js';
27
- export { AuthAdapterError, BetterAuthAdapter, createBetterAuthAdapter, } from './better-auth.js';
47
+ export { BetterAuthAdapter, createBetterAuthAdapter } from './better-auth.js';
@@ -1,13 +1,31 @@
1
1
  /**
2
- * Auth Adapters - External authentication provider integrations
2
+ * Auth Adapters - Authentication provider integrations
3
3
  *
4
- * This module exports adapters for integrating external authentication
5
- * providers with VeloxTS. Each adapter implements the AuthAdapter interface
6
- * and can be used with createAuthAdapterPlugin.
4
+ * This module exports adapters for integrating authentication providers
5
+ * with VeloxTS. Each adapter implements the AuthAdapter interface and
6
+ * can be used with createAuthAdapterPlugin.
7
+ *
8
+ * Available adapters:
9
+ * - **JwtAdapter** - Built-in JWT authentication using the adapter pattern
10
+ * - **BetterAuthAdapter** - Integration with BetterAuth library
7
11
  *
8
12
  * @module auth/adapters
9
13
  *
10
- * @example
14
+ * @example JWT Adapter
15
+ * ```typescript
16
+ * import { createJwtAdapter } from '@veloxts/auth/adapters';
17
+ * import { createAuthAdapterPlugin } from '@veloxts/auth';
18
+ *
19
+ * const { adapter, config } = createJwtAdapter({
20
+ * jwt: { secret: process.env.JWT_SECRET! },
21
+ * userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
22
+ * });
23
+ *
24
+ * const plugin = createAuthAdapterPlugin({ adapter, config });
25
+ * app.use(plugin);
26
+ * ```
27
+ *
28
+ * @example BetterAuth Adapter
11
29
  * ```typescript
12
30
  * import { createBetterAuthAdapter } from '@veloxts/auth/adapters';
13
31
  * import { createAuthAdapterPlugin } from '@veloxts/auth';
@@ -23,5 +41,5 @@
23
41
  * });
24
42
  * ```
25
43
  */
26
- // BetterAuth Adapter
27
- export { AuthAdapterError, BetterAuthAdapter, createBetterAuthAdapter, } from './better-auth.js';
44
+ export { AuthAdapterError, createJwtAdapter, JwtAdapter } from './jwt-adapter.js';
45
+ export { BetterAuthAdapter, createBetterAuthAdapter } from './better-auth.js';
@@ -0,0 +1,261 @@
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 type { FastifyInstance, FastifyRequest } from 'fastify';
42
+ import type { AdapterRoute, AdapterSessionResult, AuthAdapterConfig } from '../adapter.js';
43
+ import { BaseAuthAdapter } from '../adapter.js';
44
+ import type { TokenStore } from '../jwt.js';
45
+ import { JwtManager } from '../jwt.js';
46
+ import type { JwtConfig, TokenPair, User } from '../types.js';
47
+ /**
48
+ * JWT adapter configuration
49
+ *
50
+ * Extends the base AuthAdapterConfig with JWT-specific options.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const config: JwtAdapterConfig = {
55
+ * name: 'jwt',
56
+ * jwt: {
57
+ * secret: process.env.JWT_SECRET!,
58
+ * accessTokenExpiry: '15m',
59
+ * refreshTokenExpiry: '7d',
60
+ * },
61
+ * userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
62
+ * enableRoutes: true,
63
+ * routePrefix: '/api/auth',
64
+ * };
65
+ * ```
66
+ */
67
+ export interface JwtAdapterConfig extends AuthAdapterConfig {
68
+ /**
69
+ * JWT configuration (secret, expiry, etc.)
70
+ *
71
+ * This is passed directly to JwtManager.
72
+ */
73
+ jwt: JwtConfig;
74
+ /**
75
+ * Token store for revocation tracking
76
+ *
77
+ * Used to check if tokens have been revoked (e.g., on logout).
78
+ * Defaults to in-memory store (not suitable for production).
79
+ *
80
+ * For production, use Redis or database-backed storage.
81
+ */
82
+ tokenStore?: TokenStore;
83
+ /**
84
+ * Load user by ID
85
+ *
86
+ * Called when verifying tokens to load the full user object.
87
+ * If not provided, a minimal user object is created from token claims.
88
+ *
89
+ * @param userId - The user ID from the token's `sub` claim
90
+ * @returns The user object or null if not found
91
+ */
92
+ userLoader?: (userId: string) => Promise<User | null>;
93
+ /**
94
+ * Enable built-in auth routes
95
+ *
96
+ * When true, mounts routes for token refresh and logout:
97
+ * - POST `${routePrefix}/refresh` - Refresh access token
98
+ * - POST `${routePrefix}/logout` - Revoke current token
99
+ *
100
+ * @default true
101
+ */
102
+ enableRoutes?: boolean;
103
+ /**
104
+ * Base path for auth routes
105
+ *
106
+ * Only used when `enableRoutes` is true.
107
+ *
108
+ * @default '/api/auth'
109
+ */
110
+ routePrefix?: string;
111
+ }
112
+ /**
113
+ * JWT Authentication Adapter
114
+ *
115
+ * Implements the AuthAdapter interface using JWT tokens.
116
+ * Provides session loading from Authorization headers and
117
+ * optional routes for token refresh and logout.
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * const adapter = new JwtAdapter();
122
+ * await adapter.initialize(fastify, {
123
+ * name: 'jwt',
124
+ * jwt: { secret: process.env.JWT_SECRET! },
125
+ * });
126
+ *
127
+ * // Get session from request
128
+ * const session = await adapter.getSession(request);
129
+ * if (session) {
130
+ * console.log('User:', session.user.email);
131
+ * }
132
+ * ```
133
+ */
134
+ export declare class JwtAdapter extends BaseAuthAdapter<JwtAdapterConfig> {
135
+ private jwt;
136
+ private tokenStore;
137
+ private userLoader?;
138
+ private enableRoutes;
139
+ private routePrefix;
140
+ constructor();
141
+ /**
142
+ * Initialize the adapter with JWT configuration
143
+ *
144
+ * Sets up the JwtManager, token store, and configuration options.
145
+ * Also exposes `jwtManager` and `tokenStore` on the Fastify instance.
146
+ */
147
+ initialize(fastify: FastifyInstance, config: JwtAdapterConfig): Promise<void>;
148
+ /**
149
+ * Get session from JWT token in Authorization header
150
+ *
151
+ * Extracts and verifies the JWT token, checks revocation status,
152
+ * and loads the user if a userLoader is configured.
153
+ *
154
+ * @returns Session result with user and session data, or null if not authenticated
155
+ */
156
+ getSession(request: FastifyRequest): Promise<AdapterSessionResult | null>;
157
+ /**
158
+ * Get routes for token refresh and logout
159
+ *
160
+ * Returns routes only if `enableRoutes` is true in config.
161
+ */
162
+ getRoutes(): AdapterRoute[];
163
+ /**
164
+ * Handle token refresh requests
165
+ *
166
+ * Expects `refreshToken` in request body.
167
+ * Returns new token pair on success.
168
+ */
169
+ private handleRefresh;
170
+ /**
171
+ * Handle logout requests
172
+ *
173
+ * Revokes the current access token by adding its JTI to the token store.
174
+ * The token is extracted from the Authorization header.
175
+ */
176
+ private handleLogout;
177
+ /**
178
+ * Clean up adapter resources
179
+ */
180
+ cleanup(): Promise<void>;
181
+ /**
182
+ * Create a token pair for a user
183
+ *
184
+ * Convenience method that delegates to the underlying JwtManager.
185
+ * Can be accessed via `fastify.jwtManager.createTokenPair()` as well.
186
+ *
187
+ * @param user - The user to create tokens for
188
+ * @param additionalClaims - Custom claims to include in the token
189
+ * @returns Token pair with access and refresh tokens
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const tokens = adapter.createTokenPair(user);
194
+ * // { accessToken, refreshToken, expiresIn, tokenType }
195
+ * ```
196
+ */
197
+ createTokenPair(user: User, additionalClaims?: Record<string, unknown>): TokenPair;
198
+ /**
199
+ * Get the underlying JwtManager instance
200
+ *
201
+ * Useful for advanced token operations.
202
+ */
203
+ getJwtManager(): JwtManager;
204
+ /**
205
+ * Get the token store instance
206
+ *
207
+ * Useful for manual token revocation.
208
+ */
209
+ getTokenStore(): TokenStore;
210
+ }
211
+ /**
212
+ * Create a JWT auth adapter
213
+ *
214
+ * This is the recommended way to create a JWT adapter for use with
215
+ * createAuthAdapterPlugin. Returns both the adapter instance and
216
+ * the configuration for convenience.
217
+ *
218
+ * @param config - JWT adapter configuration (without name, which is auto-set to 'jwt')
219
+ * @returns Object with adapter and config
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * import { createJwtAdapter } from '@veloxts/auth/adapters/jwt-adapter';
224
+ * import { createAuthAdapterPlugin } from '@veloxts/auth';
225
+ *
226
+ * const { adapter, config } = createJwtAdapter({
227
+ * jwt: {
228
+ * secret: process.env.JWT_SECRET!,
229
+ * accessTokenExpiry: '15m',
230
+ * refreshTokenExpiry: '7d',
231
+ * },
232
+ * userLoader: async (userId) => db.user.findUnique({ where: { id: userId } }),
233
+ * });
234
+ *
235
+ * const authPlugin = createAuthAdapterPlugin({ adapter, config });
236
+ * app.use(authPlugin);
237
+ * ```
238
+ */
239
+ export declare function createJwtAdapter(config: Omit<JwtAdapterConfig, 'name'>): {
240
+ adapter: JwtAdapter;
241
+ config: JwtAdapterConfig;
242
+ };
243
+ declare module 'fastify' {
244
+ interface FastifyInstance {
245
+ /**
246
+ * JWT manager instance (from JwtAdapter)
247
+ *
248
+ * Available after registering the JWT adapter plugin.
249
+ * Use for creating tokens, verifying tokens, etc.
250
+ */
251
+ jwtManager?: JwtManager;
252
+ /**
253
+ * Token store instance (from JwtAdapter)
254
+ *
255
+ * Available after registering the JWT adapter plugin.
256
+ * Use for manual token revocation.
257
+ */
258
+ tokenStore?: TokenStore;
259
+ }
260
+ }
261
+ export { AuthAdapterError } from '../adapter.js';