@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.
@@ -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
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Narrowing Guards (Experimental)
3
+ *
4
+ * These guards provide TypeScript type narrowing after they pass.
5
+ * When using `guardNarrow(authenticatedNarrow)`, the context type
6
+ * is narrowed to guarantee `ctx.user` is non-null.
7
+ *
8
+ * EXPERIMENTAL: This API may change. The current recommended approach
9
+ * is to use middleware for context type extension.
10
+ *
11
+ * @module auth/guards-narrowing
12
+ */
13
+ import type { AuthContext, GuardFunction, User } from './types.js';
14
+ /**
15
+ * A guard that narrows the context type after passing.
16
+ *
17
+ * The `_narrows` phantom type indicates what the guard guarantees
18
+ * about the context after it passes.
19
+ *
20
+ * @template TRequired - Context properties required to run the guard
21
+ * @template TGuaranteed - Context properties guaranteed after guard passes
22
+ */
23
+ export interface NarrowingGuard<TRequired, TGuaranteed> {
24
+ /** Guard name for error messages */
25
+ name: string;
26
+ /** Guard check function (matches GuardFunction signature) */
27
+ check: GuardFunction<TRequired>;
28
+ /** Custom error message */
29
+ message?: string;
30
+ /** HTTP status code for guard failures */
31
+ statusCode?: number;
32
+ /**
33
+ * Phantom type declaring what the guard guarantees.
34
+ * Used by ProcedureBuilder.guardNarrow() for type narrowing.
35
+ * @internal
36
+ */
37
+ readonly _narrows: TGuaranteed;
38
+ }
39
+ /**
40
+ * Context type with a guaranteed authenticated user.
41
+ *
42
+ * After `authenticatedNarrow` passes, the context is narrowed to this type.
43
+ */
44
+ export interface AuthenticatedContext {
45
+ auth: AuthContext & {
46
+ isAuthenticated: true;
47
+ };
48
+ user: User;
49
+ }
50
+ /**
51
+ * Context type with a guaranteed user having specific roles.
52
+ */
53
+ export interface RoleNarrowedContext {
54
+ user: User & {
55
+ roles: string[];
56
+ };
57
+ }
58
+ /**
59
+ * Authenticated guard with type narrowing.
60
+ *
61
+ * When used with `guardNarrow()`, narrows `ctx.user` from `User | undefined`
62
+ * to `User`, eliminating the need for null checks in the handler.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import { authenticatedNarrow } from '@veloxts/auth';
67
+ *
68
+ * // With guardNarrow (experimental):
69
+ * procedure()
70
+ * .guardNarrow(authenticatedNarrow)
71
+ * .query(({ ctx }) => {
72
+ * // ctx.user is typed as User (non-null)
73
+ * return { email: ctx.user.email };
74
+ * });
75
+ *
76
+ * // Current recommended alternative using middleware:
77
+ * procedure()
78
+ * .guard(authenticated)
79
+ * .use(async ({ ctx, next }) => {
80
+ * if (!ctx.user) throw new Error('Unreachable');
81
+ * return next({ ctx: { user: ctx.user } });
82
+ * })
83
+ * .query(({ ctx }) => {
84
+ * // ctx.user is non-null via middleware
85
+ * });
86
+ * ```
87
+ */
88
+ export declare const authenticatedNarrow: NarrowingGuard<{
89
+ auth?: AuthContext;
90
+ }, AuthenticatedContext>;
91
+ /**
92
+ * Creates a role-checking guard with type narrowing.
93
+ *
94
+ * Narrows `ctx.user` to guarantee non-null with roles array.
95
+ *
96
+ * @param roles - Required role(s)
97
+ * @returns NarrowingGuard that guarantees user with roles
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * import { hasRoleNarrow } from '@veloxts/auth';
102
+ *
103
+ * procedure()
104
+ * .guardNarrow(hasRoleNarrow('admin'))
105
+ * .mutation(({ ctx }) => {
106
+ * // ctx.user is typed as User (non-null)
107
+ * // ctx.user.roles is string[]
108
+ * });
109
+ * ```
110
+ */
111
+ export declare function hasRoleNarrow(roles: string | string[]): NarrowingGuard<{
112
+ user?: User;
113
+ }, RoleNarrowedContext>;
114
+ /**
115
+ * Extracts the narrowed context type from a NarrowingGuard.
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * type Ctx = InferNarrowedContext<typeof authenticatedNarrow>;
120
+ * // Ctx = AuthenticatedContext = { auth: AuthContext & { isAuthenticated: true }; user: User }
121
+ * ```
122
+ */
123
+ export type InferNarrowedContext<T> = T extends NarrowingGuard<unknown, infer U> ? U : never;