@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.
- package/README.md +590 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +637 -0
- package/dist/index.js.map +1 -0
- package/dist/testing/index.d.ts +104 -0
- package/dist/testing/index.js +52 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-IH8aZeWZ.d.ts +311 -0
- package/package.json +69 -0
- package/src/auth-interceptor.ts +137 -0
- package/src/authz-interceptor.ts +158 -0
- package/src/cache.ts +66 -0
- package/src/context.ts +63 -0
- package/src/errors.ts +45 -0
- package/src/gateway-auth-interceptor.ts +203 -0
- package/src/headers.ts +149 -0
- package/src/index.ts +49 -0
- package/src/jwt-auth-interceptor.ts +208 -0
- package/src/method-match.ts +46 -0
- package/src/session-auth-interceptor.ts +120 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/mock-context.ts +44 -0
- package/src/testing/test-jwt.ts +75 -0
- package/src/testing/with-context.ts +33 -0
- package/src/types.ts +326 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|