@connectum/auth 1.0.0-rc.3

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,388 @@
1
+ import { Interceptor, ConnectError } from '@connectrpc/connect';
2
+ import { A as AuthInterceptorOptions, a as AuthzInterceptorOptions, b as AuthContext, G as GatewayAuthInterceptorOptions, J as JwtAuthInterceptorOptions, S as SessionAuthInterceptorOptions } from './types-IH8aZeWZ.js';
3
+ export { c as AUTH_HEADERS, d as AuthzEffect, e as AuthzRule, C as CacheOptions, f as GatewayHeaderMapping, I as InterceptorFactory } from './types-IH8aZeWZ.js';
4
+ import { AsyncLocalStorage } from 'node:async_hooks';
5
+ import { SanitizableError } from '@connectum/core';
6
+
7
+ /**
8
+ * Generic authentication interceptor
9
+ *
10
+ * Provides pluggable authentication for any credential type.
11
+ * Extracts credentials, verifies them, and stores AuthContext
12
+ * in AsyncLocalStorage for downstream access.
13
+ *
14
+ * @module auth-interceptor
15
+ */
16
+
17
+ /**
18
+ * Create a generic authentication interceptor.
19
+ *
20
+ * Extracts credentials from request headers, verifies them using
21
+ * a user-provided callback, and stores the resulting AuthContext
22
+ * in AsyncLocalStorage for downstream access.
23
+ *
24
+ * @param options - Authentication options
25
+ * @returns ConnectRPC interceptor
26
+ *
27
+ * @example API key authentication
28
+ * ```typescript
29
+ * import { createAuthInterceptor } from '@connectum/auth';
30
+ *
31
+ * const auth = createAuthInterceptor({
32
+ * extractCredentials: (req) => req.header.get('x-api-key'),
33
+ * verifyCredentials: async (apiKey) => {
34
+ * const user = await db.findByApiKey(apiKey);
35
+ * if (!user) throw new Error('Invalid API key');
36
+ * return {
37
+ * subject: user.id,
38
+ * roles: user.roles,
39
+ * scopes: [],
40
+ * claims: {},
41
+ * type: 'api-key',
42
+ * };
43
+ * },
44
+ * });
45
+ * ```
46
+ *
47
+ * @example Bearer token with default extractor
48
+ * ```typescript
49
+ * const auth = createAuthInterceptor({
50
+ * verifyCredentials: async (token) => {
51
+ * const payload = await verifyToken(token);
52
+ * return {
53
+ * subject: payload.sub,
54
+ * roles: payload.roles ?? [],
55
+ * scopes: payload.scope?.split(' ') ?? [],
56
+ * claims: payload,
57
+ * type: 'jwt',
58
+ * };
59
+ * },
60
+ * });
61
+ * ```
62
+ */
63
+ declare function createAuthInterceptor(options: AuthInterceptorOptions): Interceptor;
64
+
65
+ /**
66
+ * Authorization interceptor
67
+ *
68
+ * Declarative rules-based authorization with RBAC support.
69
+ * Evaluates rules against AuthContext from the auth interceptor.
70
+ *
71
+ * @module authz-interceptor
72
+ */
73
+
74
+ /**
75
+ * Create an authorization interceptor.
76
+ *
77
+ * Evaluates declarative rules and/or a programmatic callback against
78
+ * the AuthContext established by the authentication interceptor.
79
+ *
80
+ * IMPORTANT: This interceptor MUST run AFTER an authentication interceptor
81
+ * in the chain.
82
+ *
83
+ * @param options - Authorization options
84
+ * @returns ConnectRPC interceptor
85
+ *
86
+ * @example RBAC with declarative rules
87
+ * ```typescript
88
+ * import { createAuthzInterceptor } from '@connectum/auth';
89
+ *
90
+ * const authz = createAuthzInterceptor({
91
+ * defaultPolicy: 'deny',
92
+ * rules: [
93
+ * { name: 'public', methods: ['public.v1.PublicService/*'], effect: 'allow' },
94
+ * { name: 'admin', methods: ['admin.v1.AdminService/*'], requires: { roles: ['admin'] }, effect: 'allow' },
95
+ * ],
96
+ * });
97
+ * ```
98
+ */
99
+ declare function createAuthzInterceptor(options?: AuthzInterceptorOptions): Interceptor;
100
+
101
+ /**
102
+ * Minimal in-memory LRU cache with TTL expiration.
103
+ *
104
+ * Uses Map insertion order for LRU eviction.
105
+ * No external dependencies.
106
+ */
107
+ declare class LruCache<T> {
108
+ #private;
109
+ constructor(options: {
110
+ ttl: number;
111
+ maxSize?: number | undefined;
112
+ });
113
+ get(key: string): T | undefined;
114
+ set(key: string, value: T): void;
115
+ clear(): void;
116
+ get size(): number;
117
+ }
118
+
119
+ /**
120
+ * Authentication context storage
121
+ *
122
+ * Uses AsyncLocalStorage to make auth context available to handlers
123
+ * without passing it through function parameters.
124
+ *
125
+ * @module context
126
+ */
127
+
128
+ /**
129
+ * Module-level AsyncLocalStorage for auth context.
130
+ *
131
+ * Set by auth interceptors, read by handlers via getAuthContext().
132
+ * Automatically isolated per async context (request).
133
+ */
134
+ declare const authContextStorage: AsyncLocalStorage<AuthContext>;
135
+ /**
136
+ * Get the current auth context.
137
+ *
138
+ * Returns the AuthContext set by the auth interceptor in the current
139
+ * async context. Returns undefined if no auth interceptor is active
140
+ * or the current method was skipped.
141
+ *
142
+ * @returns Current auth context or undefined
143
+ *
144
+ * @example Usage in a service handler
145
+ * ```typescript
146
+ * import { getAuthContext } from '@connectum/auth';
147
+ *
148
+ * const handler = {
149
+ * async getUser(req) {
150
+ * const auth = getAuthContext();
151
+ * if (!auth) throw new ConnectError('Not authenticated', Code.Unauthenticated);
152
+ * return { user: await db.getUser(auth.subject) };
153
+ * },
154
+ * };
155
+ * ```
156
+ */
157
+ declare function getAuthContext(): AuthContext | undefined;
158
+ /**
159
+ * Get the current auth context or throw.
160
+ *
161
+ * Like getAuthContext() but throws ConnectError(Code.Unauthenticated)
162
+ * if no auth context is available. Use when auth is mandatory.
163
+ *
164
+ * @returns Current auth context (never undefined)
165
+ * @throws ConnectError with Code.Unauthenticated if no context
166
+ */
167
+ declare function requireAuthContext(): AuthContext;
168
+
169
+ /**
170
+ * Auth-specific error types
171
+ *
172
+ * @module errors
173
+ */
174
+
175
+ /**
176
+ * Details for authorization denied errors.
177
+ */
178
+ interface AuthzDeniedDetails {
179
+ readonly ruleName: string;
180
+ readonly requiredRoles?: readonly string[];
181
+ readonly requiredScopes?: readonly string[];
182
+ }
183
+ /**
184
+ * Authorization denied error.
185
+ *
186
+ * Carries server-side details (rule name, required roles/scopes) while
187
+ * exposing only "Access denied" to the client via SanitizableError protocol.
188
+ */
189
+ declare class AuthzDeniedError extends ConnectError implements SanitizableError {
190
+ readonly clientMessage = "Access denied";
191
+ readonly ruleName: string;
192
+ readonly authzDetails: AuthzDeniedDetails;
193
+ get serverDetails(): Readonly<Record<string, unknown>>;
194
+ constructor(details: AuthzDeniedDetails);
195
+ }
196
+
197
+ /**
198
+ * Gateway authentication interceptor
199
+ *
200
+ * For services behind an API gateway that has already performed authentication.
201
+ * Extracts auth context from gateway-injected headers after verifying trust.
202
+ *
203
+ * Trust is established via a header (e.g., x-gateway-secret) rather than
204
+ * peerAddress, since ConnectRPC interceptors don't have access to peer info.
205
+ *
206
+ * @module gateway-auth-interceptor
207
+ */
208
+
209
+ /**
210
+ * Create a gateway authentication interceptor.
211
+ *
212
+ * Reads pre-authenticated identity from gateway-injected headers.
213
+ * Trust is established by checking a designated header value against
214
+ * a list of expected values (shared secrets or trusted IP ranges).
215
+ *
216
+ * @param options - Gateway auth configuration
217
+ * @returns ConnectRPC interceptor
218
+ *
219
+ * @example Kong/Envoy gateway with shared secret
220
+ * ```typescript
221
+ * const gatewayAuth = createGatewayAuthInterceptor({
222
+ * headerMapping: {
223
+ * subject: 'x-user-id',
224
+ * name: 'x-user-name',
225
+ * roles: 'x-user-roles',
226
+ * },
227
+ * trustSource: {
228
+ * header: 'x-gateway-secret',
229
+ * expectedValues: [process.env.GATEWAY_SECRET],
230
+ * },
231
+ * });
232
+ * ```
233
+ */
234
+ declare function createGatewayAuthInterceptor(options: GatewayAuthInterceptorOptions): Interceptor;
235
+
236
+ /**
237
+ * Auth header propagation utilities
238
+ *
239
+ * Handles serialization/deserialization of AuthContext to/from
240
+ * HTTP headers for cross-service context propagation.
241
+ *
242
+ * @module headers
243
+ */
244
+
245
+ /**
246
+ * Serialize AuthContext to request headers.
247
+ *
248
+ * Sets standard auth headers on the provided Headers object.
249
+ * Used by auth interceptors when propagateHeaders is enabled.
250
+ *
251
+ * @param headers - Headers object to set auth headers on
252
+ * @param context - Auth context to serialize
253
+ * @param propagatedClaims - Optional list of claim keys to propagate (all if undefined)
254
+ */
255
+ declare function setAuthHeaders(headers: Headers, context: AuthContext, propagatedClaims?: string[]): void;
256
+ /**
257
+ * Parse AuthContext from request headers.
258
+ *
259
+ * Deserializes auth context from standard headers set by an upstream
260
+ * service or gateway. Returns undefined if required headers are missing.
261
+ *
262
+ * WARNING: Only use this in trusted environments (behind mTLS, mesh, etc.).
263
+ * For untrusted environments, use createTrustedHeadersReader() instead.
264
+ *
265
+ * @param headers - Request headers to parse
266
+ * @returns Parsed AuthContext or undefined if headers are missing
267
+ *
268
+ * @example Trust upstream auth headers
269
+ * ```typescript
270
+ * import { parseAuthHeaders } from '@connectum/auth';
271
+ *
272
+ * const context = parseAuthHeaders(req.header);
273
+ * if (context) {
274
+ * console.log(`Authenticated as ${context.subject}`);
275
+ * }
276
+ * ```
277
+ */
278
+ declare function parseAuthHeaders(headers: Headers): AuthContext | undefined;
279
+
280
+ /**
281
+ * JWT authentication interceptor
282
+ *
283
+ * Convenience wrapper for JWT-based authentication using the jose library.
284
+ * Supports JWKS remote key sets, HMAC secrets, and asymmetric public keys.
285
+ *
286
+ * @module jwt-auth-interceptor
287
+ */
288
+
289
+ /**
290
+ * Create a JWT authentication interceptor.
291
+ *
292
+ * Convenience wrapper around createAuthInterceptor() that handles
293
+ * JWT extraction from Authorization header, verification via jose,
294
+ * and standard claim mapping to AuthContext.
295
+ *
296
+ * @param options - JWT authentication options
297
+ * @returns ConnectRPC interceptor
298
+ *
299
+ * @example JWKS-based JWT auth (Auth0, Keycloak, etc.)
300
+ * ```typescript
301
+ * import { createJwtAuthInterceptor } from '@connectum/auth';
302
+ *
303
+ * const jwtAuth = createJwtAuthInterceptor({
304
+ * jwksUri: 'https://auth.example.com/.well-known/jwks.json',
305
+ * issuer: 'https://auth.example.com/',
306
+ * audience: 'my-api',
307
+ * claimsMapping: {
308
+ * roles: 'realm_access.roles',
309
+ * scopes: 'scope',
310
+ * },
311
+ * });
312
+ * ```
313
+ *
314
+ * @example HMAC secret (testing / simple setups)
315
+ * ```typescript
316
+ * const jwtAuth = createJwtAuthInterceptor({
317
+ * secret: process.env.JWT_SECRET,
318
+ * issuer: 'my-service',
319
+ * });
320
+ * ```
321
+ */
322
+ declare function createJwtAuthInterceptor(options: JwtAuthInterceptorOptions): Interceptor;
323
+
324
+ /**
325
+ * Method pattern matching utility
326
+ *
327
+ * Shared logic for matching gRPC methods against patterns.
328
+ * Used by both auth and authz interceptors.
329
+ *
330
+ * @module method-match
331
+ */
332
+ /**
333
+ * Check if a method matches any of the given patterns.
334
+ *
335
+ * Patterns:
336
+ * - "*" — matches all methods
337
+ * - "Service/*" — matches all methods of a service
338
+ * - "Service/Method" — matches exact method
339
+ *
340
+ * @param serviceName - Fully-qualified service name (e.g., "user.v1.UserService")
341
+ * @param methodName - Method name (e.g., "GetUser")
342
+ * @param patterns - Readonly array of match patterns
343
+ * @returns true if the method matches any pattern
344
+ */
345
+ declare function matchesMethodPattern(serviceName: string, methodName: string, patterns: readonly string[]): boolean;
346
+
347
+ /**
348
+ * Session-based authentication interceptor
349
+ *
350
+ * Convenience wrapper for session-based auth systems (e.g., better-auth).
351
+ * Implements interceptor directly (not via createAuthInterceptor) to pass
352
+ * full request headers to verifySession for cookie-based auth support.
353
+ *
354
+ * @module session-auth-interceptor
355
+ */
356
+
357
+ /**
358
+ * Create a session-based authentication interceptor.
359
+ *
360
+ * Two-step authentication:
361
+ * 1. Extract token from request
362
+ * 2. Verify session via user-provided callback (receives full headers for cookie support)
363
+ * 3. Map session data to AuthContext via user-provided mapper
364
+ *
365
+ * @param options - Session auth configuration
366
+ * @returns ConnectRPC interceptor
367
+ *
368
+ * @example better-auth integration
369
+ * ```typescript
370
+ * import { createSessionAuthInterceptor } from '@connectum/auth';
371
+ *
372
+ * const sessionAuth = createSessionAuthInterceptor({
373
+ * verifySession: (token, headers) => auth.api.getSession({ headers }),
374
+ * mapSession: (s) => ({
375
+ * subject: s.user.id,
376
+ * name: s.user.name,
377
+ * roles: [],
378
+ * scopes: [],
379
+ * claims: s.user,
380
+ * type: 'session',
381
+ * }),
382
+ * cache: { ttl: 60_000 },
383
+ * });
384
+ * ```
385
+ */
386
+ declare function createSessionAuthInterceptor(options: SessionAuthInterceptorOptions): Interceptor;
387
+
388
+ export { AuthContext, AuthInterceptorOptions, type AuthzDeniedDetails, AuthzDeniedError, AuthzInterceptorOptions, GatewayAuthInterceptorOptions, JwtAuthInterceptorOptions, LruCache, SessionAuthInterceptorOptions, authContextStorage, createAuthInterceptor, createAuthzInterceptor, createGatewayAuthInterceptor, createJwtAuthInterceptor, createSessionAuthInterceptor, getAuthContext, matchesMethodPattern, parseAuthHeaders, requireAuthContext, setAuthHeaders };