@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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-based authentication interceptor
|
|
3
|
+
*
|
|
4
|
+
* Convenience wrapper for session-based auth systems (e.g., better-auth).
|
|
5
|
+
* Implements interceptor directly (not via createAuthInterceptor) to pass
|
|
6
|
+
* full request headers to verifySession for cookie-based auth support.
|
|
7
|
+
*
|
|
8
|
+
* @module session-auth-interceptor
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Interceptor, StreamRequest, UnaryRequest } from "@connectrpc/connect";
|
|
12
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
13
|
+
import { LruCache } from "./cache.ts";
|
|
14
|
+
import { authContextStorage } from "./context.ts";
|
|
15
|
+
import { setAuthHeaders } from "./headers.ts";
|
|
16
|
+
import { matchesMethodPattern } from "./method-match.ts";
|
|
17
|
+
import type { AuthContext, SessionAuthInterceptorOptions } from "./types.ts";
|
|
18
|
+
import { AUTH_HEADERS } from "./types.ts";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default token extractor.
|
|
22
|
+
* Extracts Bearer token from Authorization header.
|
|
23
|
+
*/
|
|
24
|
+
function defaultExtractToken(req: { header: Headers }): string | null {
|
|
25
|
+
const authHeader = req.header.get("authorization");
|
|
26
|
+
if (!authHeader) return null;
|
|
27
|
+
const match = /^Bearer\s+(.+)$/i.exec(authHeader);
|
|
28
|
+
return match?.[1] ?? null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a session-based authentication interceptor.
|
|
33
|
+
*
|
|
34
|
+
* Two-step authentication:
|
|
35
|
+
* 1. Extract token from request
|
|
36
|
+
* 2. Verify session via user-provided callback (receives full headers for cookie support)
|
|
37
|
+
* 3. Map session data to AuthContext via user-provided mapper
|
|
38
|
+
*
|
|
39
|
+
* @param options - Session auth configuration
|
|
40
|
+
* @returns ConnectRPC interceptor
|
|
41
|
+
*
|
|
42
|
+
* @example better-auth integration
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { createSessionAuthInterceptor } from '@connectum/auth';
|
|
45
|
+
*
|
|
46
|
+
* const sessionAuth = createSessionAuthInterceptor({
|
|
47
|
+
* verifySession: (token, headers) => auth.api.getSession({ headers }),
|
|
48
|
+
* mapSession: (s) => ({
|
|
49
|
+
* subject: s.user.id,
|
|
50
|
+
* name: s.user.name,
|
|
51
|
+
* roles: [],
|
|
52
|
+
* scopes: [],
|
|
53
|
+
* claims: s.user,
|
|
54
|
+
* type: 'session',
|
|
55
|
+
* }),
|
|
56
|
+
* cache: { ttl: 60_000 },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function createSessionAuthInterceptor(options: SessionAuthInterceptorOptions): Interceptor {
|
|
61
|
+
const { verifySession, mapSession, extractToken = defaultExtractToken, cache: cacheOptions, skipMethods = [], propagateHeaders = false, propagatedClaims } = options;
|
|
62
|
+
|
|
63
|
+
const cache = cacheOptions ? new LruCache<AuthContext>(cacheOptions) : undefined;
|
|
64
|
+
|
|
65
|
+
return (next) => async (req: UnaryRequest | StreamRequest) => {
|
|
66
|
+
const serviceName: string = req.service.typeName;
|
|
67
|
+
const methodName: string = req.method.name;
|
|
68
|
+
|
|
69
|
+
// Strip auth headers to prevent spoofing
|
|
70
|
+
for (const headerName of Object.values(AUTH_HEADERS)) {
|
|
71
|
+
req.header.delete(headerName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (matchesMethodPattern(serviceName, methodName, skipMethods)) {
|
|
75
|
+
return await next(req);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Extract token
|
|
79
|
+
const token = await extractToken(req);
|
|
80
|
+
if (!token) {
|
|
81
|
+
throw new ConnectError("Missing credentials", Code.Unauthenticated);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check cache
|
|
85
|
+
const cached = cache?.get(token);
|
|
86
|
+
if (cached && (!cached.expiresAt || cached.expiresAt.getTime() > Date.now())) {
|
|
87
|
+
if (propagateHeaders) {
|
|
88
|
+
setAuthHeaders(req.header, cached, propagatedClaims);
|
|
89
|
+
}
|
|
90
|
+
return await authContextStorage.run(cached, () => next(req));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Verify session — pass full headers for cookie-based auth
|
|
94
|
+
let session: unknown;
|
|
95
|
+
try {
|
|
96
|
+
session = await verifySession(token, req.header);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
if (err instanceof ConnectError) throw err;
|
|
99
|
+
throw new ConnectError("Session verification failed", Code.Unauthenticated);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Map session to AuthContext
|
|
103
|
+
let authContext: AuthContext;
|
|
104
|
+
try {
|
|
105
|
+
authContext = await mapSession(session);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (err instanceof ConnectError) throw err;
|
|
108
|
+
throw new ConnectError("Session mapping failed", Code.Unauthenticated);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Cache result
|
|
112
|
+
cache?.set(token, authContext);
|
|
113
|
+
|
|
114
|
+
if (propagateHeaders) {
|
|
115
|
+
setAuthHeaders(req.header, authContext, propagatedClaims);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return await authContextStorage.run(authContext, () => next(req));
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @connectum/auth/testing
|
|
3
|
+
*
|
|
4
|
+
* Test utilities for authentication and authorization.
|
|
5
|
+
*
|
|
6
|
+
* @module @connectum/auth/testing
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { createMockAuthContext } from "./mock-context.ts";
|
|
10
|
+
export { createTestJwt, TEST_JWT_SECRET } from "./test-jwt.ts";
|
|
11
|
+
export { withAuthContext } from "./with-context.ts";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock auth context for testing
|
|
3
|
+
*
|
|
4
|
+
* @module testing/mock-context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AuthContext } from "../types.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default mock auth context values.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_MOCK_CONTEXT: AuthContext = {
|
|
13
|
+
subject: "test-user",
|
|
14
|
+
name: "Test User",
|
|
15
|
+
roles: ["user"],
|
|
16
|
+
scopes: ["read"],
|
|
17
|
+
claims: {},
|
|
18
|
+
type: "test",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a mock AuthContext for testing.
|
|
23
|
+
*
|
|
24
|
+
* Merges provided overrides with sensible test defaults.
|
|
25
|
+
*
|
|
26
|
+
* @param overrides - Partial AuthContext to override defaults
|
|
27
|
+
* @returns Complete AuthContext
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { createMockAuthContext } from '@connectum/auth/testing';
|
|
32
|
+
*
|
|
33
|
+
* const ctx = createMockAuthContext({ subject: 'admin-1', roles: ['admin'] });
|
|
34
|
+
* assert.strictEqual(ctx.subject, 'admin-1');
|
|
35
|
+
* assert.deepStrictEqual(ctx.roles, ['admin']);
|
|
36
|
+
* assert.strictEqual(ctx.type, 'test'); // default preserved
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function createMockAuthContext(overrides?: Partial<AuthContext>): AuthContext {
|
|
40
|
+
return {
|
|
41
|
+
...DEFAULT_MOCK_CONTEXT,
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test JWT utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides deterministic JWT creation for testing.
|
|
5
|
+
* NOT for production use.
|
|
6
|
+
*
|
|
7
|
+
* @module testing/test-jwt
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as jose from "jose";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Deterministic test secret for HS256 JWTs.
|
|
14
|
+
*
|
|
15
|
+
* WARNING: This is a well-known secret for testing only.
|
|
16
|
+
* NEVER use in production.
|
|
17
|
+
*/
|
|
18
|
+
export const TEST_JWT_SECRET = "connectum-test-secret-do-not-use-in-production";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Encoded test secret for jose.
|
|
22
|
+
*/
|
|
23
|
+
const encodedSecret = new TextEncoder().encode(TEST_JWT_SECRET);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a signed test JWT for integration testing.
|
|
27
|
+
*
|
|
28
|
+
* Uses HS256 algorithm with a deterministic test key.
|
|
29
|
+
* NOT for production use.
|
|
30
|
+
*
|
|
31
|
+
* @param payload - JWT claims
|
|
32
|
+
* @param options - Signing options
|
|
33
|
+
* @returns Signed JWT string
|
|
34
|
+
*
|
|
35
|
+
* @example Create a test token
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { createTestJwt, TEST_JWT_SECRET } from '@connectum/auth/testing';
|
|
38
|
+
*
|
|
39
|
+
* const token = await createTestJwt({
|
|
40
|
+
* sub: 'user-123',
|
|
41
|
+
* roles: ['admin'],
|
|
42
|
+
* scope: 'read write',
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Use with createJwtAuthInterceptor in tests
|
|
46
|
+
* const auth = createJwtAuthInterceptor({ secret: TEST_JWT_SECRET });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export async function createTestJwt(
|
|
50
|
+
payload: Record<string, unknown>,
|
|
51
|
+
options?: {
|
|
52
|
+
expiresIn?: string;
|
|
53
|
+
issuer?: string;
|
|
54
|
+
audience?: string;
|
|
55
|
+
},
|
|
56
|
+
): Promise<string> {
|
|
57
|
+
let builder = new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt();
|
|
58
|
+
|
|
59
|
+
if (options?.expiresIn) {
|
|
60
|
+
builder = builder.setExpirationTime(options.expiresIn);
|
|
61
|
+
} else {
|
|
62
|
+
// Default: 1 hour expiry
|
|
63
|
+
builder = builder.setExpirationTime("1h");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options?.issuer) {
|
|
67
|
+
builder = builder.setIssuer(options.issuer);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (options?.audience) {
|
|
71
|
+
builder = builder.setAudience(options.audience);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return await builder.sign(encodedSecret);
|
|
75
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth context test helper
|
|
3
|
+
*
|
|
4
|
+
* @module testing/with-context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { authContextStorage } from "../context.ts";
|
|
8
|
+
import type { AuthContext } from "../types.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Run a function with a pre-set AuthContext.
|
|
12
|
+
*
|
|
13
|
+
* Sets the provided AuthContext in AsyncLocalStorage for the duration
|
|
14
|
+
* of the callback. Useful for testing handlers that call getAuthContext().
|
|
15
|
+
*
|
|
16
|
+
* @param context - Auth context to set
|
|
17
|
+
* @param fn - Function to execute within the context
|
|
18
|
+
* @returns Return value of fn
|
|
19
|
+
*
|
|
20
|
+
* @example Test a handler that reads auth context
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { withAuthContext, createMockAuthContext } from '@connectum/auth/testing';
|
|
23
|
+
* import { getAuthContext } from '@connectum/auth';
|
|
24
|
+
*
|
|
25
|
+
* await withAuthContext(createMockAuthContext({ subject: 'test-user' }), async () => {
|
|
26
|
+
* const ctx = getAuthContext();
|
|
27
|
+
* assert.strictEqual(ctx?.subject, 'test-user');
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export async function withAuthContext<T>(context: AuthContext, fn: () => T | Promise<T>): Promise<T> {
|
|
32
|
+
return await authContextStorage.run(context, fn);
|
|
33
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for @connectum/auth
|
|
3
|
+
*
|
|
4
|
+
* @module types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Interceptor } from "@connectrpc/connect";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Interceptor factory function type
|
|
11
|
+
*
|
|
12
|
+
* @template TOptions - Options type for the interceptor
|
|
13
|
+
*/
|
|
14
|
+
export type InterceptorFactory<TOptions = void> = TOptions extends void ? () => Interceptor : (options: TOptions) => Interceptor;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Authenticated user context
|
|
18
|
+
*
|
|
19
|
+
* Represents the result of authentication. Set by auth interceptor,
|
|
20
|
+
* accessible via getAuthContext() in handlers and downstream interceptors.
|
|
21
|
+
*/
|
|
22
|
+
export interface AuthContext {
|
|
23
|
+
/** Authenticated subject identifier (user ID, service account, etc.) */
|
|
24
|
+
readonly subject: string;
|
|
25
|
+
/** Human-readable display name */
|
|
26
|
+
readonly name?: string | undefined;
|
|
27
|
+
/** Assigned roles (e.g., ["admin", "user"]) */
|
|
28
|
+
readonly roles: ReadonlyArray<string>;
|
|
29
|
+
/** Granted scopes (e.g., ["read", "write"]) */
|
|
30
|
+
readonly scopes: ReadonlyArray<string>;
|
|
31
|
+
/** Raw claims from the credential (JWT claims, API key metadata, etc.) */
|
|
32
|
+
readonly claims: Readonly<Record<string, unknown>>;
|
|
33
|
+
/** Credential type identifier (e.g., "jwt", "api-key", "mtls") */
|
|
34
|
+
readonly type: string;
|
|
35
|
+
/** Credential expiration time */
|
|
36
|
+
readonly expiresAt?: Date | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Standard header names for auth context propagation.
|
|
41
|
+
*
|
|
42
|
+
* Used for cross-service context propagation (similar to Envoy credential injection).
|
|
43
|
+
* The auth interceptor sets these headers when propagateHeaders is true.
|
|
44
|
+
*
|
|
45
|
+
* WARNING: These headers are trusted ONLY in service-to-service communication
|
|
46
|
+
* where transport security (mTLS) is established. Never trust these headers
|
|
47
|
+
* from external clients without using createGatewayAuthInterceptor().
|
|
48
|
+
*/
|
|
49
|
+
export const AUTH_HEADERS = {
|
|
50
|
+
/** Authenticated subject identifier */
|
|
51
|
+
SUBJECT: "x-auth-subject",
|
|
52
|
+
/** JSON-encoded roles array */
|
|
53
|
+
ROLES: "x-auth-roles",
|
|
54
|
+
/** Space-separated scopes */
|
|
55
|
+
SCOPES: "x-auth-scopes",
|
|
56
|
+
/** JSON-encoded claims object */
|
|
57
|
+
CLAIMS: "x-auth-claims",
|
|
58
|
+
/** Human-readable display name */
|
|
59
|
+
NAME: "x-auth-name",
|
|
60
|
+
/** Credential type (jwt, api-key, mtls, etc.) */
|
|
61
|
+
TYPE: "x-auth-type",
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Authorization rule effect
|
|
66
|
+
*/
|
|
67
|
+
export const AuthzEffect = {
|
|
68
|
+
ALLOW: "allow",
|
|
69
|
+
DENY: "deny",
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
export type AuthzEffect = (typeof AuthzEffect)[keyof typeof AuthzEffect];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Authorization rule definition.
|
|
76
|
+
*
|
|
77
|
+
* When a rule has `requires`, the match semantics are:
|
|
78
|
+
* - **roles**: "any-of" -- the user must have **at least one** of the listed roles.
|
|
79
|
+
* - **scopes**: "all-of" -- the user must have **every** listed scope.
|
|
80
|
+
*/
|
|
81
|
+
export interface AuthzRule {
|
|
82
|
+
/** Rule name for logging/debugging */
|
|
83
|
+
readonly name: string;
|
|
84
|
+
/** Method patterns to match (e.g., "admin.v1.AdminService/*", "user.v1.UserService/DeleteUser") */
|
|
85
|
+
readonly methods: ReadonlyArray<string>;
|
|
86
|
+
/** Effect when rule matches */
|
|
87
|
+
readonly effect: AuthzEffect;
|
|
88
|
+
/**
|
|
89
|
+
* Required roles/scopes for this rule.
|
|
90
|
+
*
|
|
91
|
+
* - `roles` uses "any-of" semantics: user needs at least one of the listed roles.
|
|
92
|
+
* - `scopes` uses "all-of" semantics: user needs every listed scope.
|
|
93
|
+
*/
|
|
94
|
+
readonly requires?:
|
|
95
|
+
| {
|
|
96
|
+
readonly roles?: ReadonlyArray<string>;
|
|
97
|
+
readonly scopes?: ReadonlyArray<string>;
|
|
98
|
+
}
|
|
99
|
+
| undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* LRU cache configuration for credentials verification
|
|
104
|
+
*/
|
|
105
|
+
export interface CacheOptions {
|
|
106
|
+
/** Cache entry time-to-live in milliseconds */
|
|
107
|
+
readonly ttl: number;
|
|
108
|
+
/** Maximum number of cached entries */
|
|
109
|
+
readonly maxSize?: number | undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generic auth interceptor options
|
|
114
|
+
*/
|
|
115
|
+
export interface AuthInterceptorOptions {
|
|
116
|
+
/**
|
|
117
|
+
* Extract credentials from request.
|
|
118
|
+
* Default: extracts Bearer token from Authorization header.
|
|
119
|
+
*
|
|
120
|
+
* @param req - Request with headers
|
|
121
|
+
* @returns Credential string or null if no credentials found
|
|
122
|
+
*/
|
|
123
|
+
extractCredentials?: (req: { header: Headers }) => string | null | Promise<string | null>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Verify credentials and return auth context.
|
|
127
|
+
* REQUIRED. Must throw on invalid credentials.
|
|
128
|
+
*
|
|
129
|
+
* @param credentials - Extracted credential string
|
|
130
|
+
* @returns AuthContext for valid credentials
|
|
131
|
+
*/
|
|
132
|
+
verifyCredentials: (credentials: string) => AuthContext | Promise<AuthContext>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Methods to skip authentication for.
|
|
136
|
+
* Patterns: "Service/Method" or "Service/*"
|
|
137
|
+
* @default [] (health and reflection methods are NOT auto-skipped)
|
|
138
|
+
*/
|
|
139
|
+
skipMethods?: string[] | undefined;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Propagate auth context as headers for downstream services.
|
|
143
|
+
* @default false
|
|
144
|
+
*/
|
|
145
|
+
propagateHeaders?: boolean | undefined;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* LRU cache for credentials verification results.
|
|
149
|
+
* Caches AuthContext by credential string to reduce verification overhead.
|
|
150
|
+
*/
|
|
151
|
+
cache?: CacheOptions | undefined;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Filter which claims are propagated in headers (SEC-001).
|
|
155
|
+
* When set, only listed claim keys are included in x-auth-claims header.
|
|
156
|
+
* When not set, all claims are propagated.
|
|
157
|
+
*/
|
|
158
|
+
propagatedClaims?: string[] | undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* JWT auth interceptor options
|
|
163
|
+
*/
|
|
164
|
+
export interface JwtAuthInterceptorOptions {
|
|
165
|
+
/** JWKS endpoint URL for remote key set */
|
|
166
|
+
jwksUri?: string | undefined;
|
|
167
|
+
/** HMAC symmetric secret (for HS256/HS384/HS512) */
|
|
168
|
+
secret?: string | undefined;
|
|
169
|
+
/** Asymmetric public key */
|
|
170
|
+
publicKey?: CryptoKey | undefined;
|
|
171
|
+
/** Expected issuer(s) */
|
|
172
|
+
issuer?: string | string[] | undefined;
|
|
173
|
+
/** Expected audience(s) */
|
|
174
|
+
audience?: string | string[] | undefined;
|
|
175
|
+
/** Allowed algorithms */
|
|
176
|
+
algorithms?: string[] | undefined;
|
|
177
|
+
/**
|
|
178
|
+
* Mapping from JWT claims to AuthContext fields.
|
|
179
|
+
* Supports dot-notation paths (e.g., "realm_access.roles").
|
|
180
|
+
*/
|
|
181
|
+
claimsMapping?:
|
|
182
|
+
| {
|
|
183
|
+
subject?: string | undefined;
|
|
184
|
+
name?: string | undefined;
|
|
185
|
+
roles?: string | undefined;
|
|
186
|
+
scopes?: string | undefined;
|
|
187
|
+
}
|
|
188
|
+
| undefined;
|
|
189
|
+
/**
|
|
190
|
+
* Maximum token age.
|
|
191
|
+
* Passed to jose jwtVerify options.
|
|
192
|
+
* Number (seconds) or string (e.g., "2h", "7d").
|
|
193
|
+
*/
|
|
194
|
+
maxTokenAge?: number | string | undefined;
|
|
195
|
+
/**
|
|
196
|
+
* Methods to skip authentication for.
|
|
197
|
+
* @default []
|
|
198
|
+
*/
|
|
199
|
+
skipMethods?: string[] | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* Propagate auth context as headers for downstream services.
|
|
202
|
+
* @default false
|
|
203
|
+
*/
|
|
204
|
+
propagateHeaders?: boolean | undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Authorization interceptor options
|
|
209
|
+
*/
|
|
210
|
+
export interface AuthzInterceptorOptions {
|
|
211
|
+
/**
|
|
212
|
+
* Default policy when no rule matches.
|
|
213
|
+
* @default "deny"
|
|
214
|
+
*/
|
|
215
|
+
defaultPolicy?: AuthzEffect | undefined;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Declarative authorization rules.
|
|
219
|
+
* Evaluated in order; first matching rule wins.
|
|
220
|
+
*/
|
|
221
|
+
rules?: AuthzRule[] | undefined;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Programmatic authorization callback.
|
|
225
|
+
* Called after rule evaluation if no rule matched,
|
|
226
|
+
* or always if no rules are defined.
|
|
227
|
+
*
|
|
228
|
+
* @param context - Authenticated user context
|
|
229
|
+
* @param req - Request info (service and method names)
|
|
230
|
+
* @returns true if authorized, false otherwise
|
|
231
|
+
*/
|
|
232
|
+
authorize?: (context: AuthContext, req: { service: string; method: string }) => boolean | Promise<boolean>;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Methods to skip authorization for.
|
|
236
|
+
* @default []
|
|
237
|
+
*/
|
|
238
|
+
skipMethods?: string[] | undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Header name mapping for gateway auth context extraction.
|
|
243
|
+
*
|
|
244
|
+
* Maps AuthContext fields to custom header names used by the API gateway.
|
|
245
|
+
*/
|
|
246
|
+
export interface GatewayHeaderMapping {
|
|
247
|
+
/** Header containing the authenticated subject */
|
|
248
|
+
readonly subject: string;
|
|
249
|
+
/** Header containing the display name */
|
|
250
|
+
readonly name?: string | undefined;
|
|
251
|
+
/** Header containing JSON-encoded roles array */
|
|
252
|
+
readonly roles?: string | undefined;
|
|
253
|
+
/** Header containing space-separated scopes */
|
|
254
|
+
readonly scopes?: string | undefined;
|
|
255
|
+
/** Header containing credential type */
|
|
256
|
+
readonly type?: string | undefined;
|
|
257
|
+
/** Header containing JSON-encoded claims */
|
|
258
|
+
readonly claims?: string | undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Gateway auth interceptor options.
|
|
263
|
+
*
|
|
264
|
+
* For services behind an API gateway that has already performed authentication.
|
|
265
|
+
* Extracts auth context from gateway-injected headers.
|
|
266
|
+
*/
|
|
267
|
+
export interface GatewayAuthInterceptorOptions {
|
|
268
|
+
/** Mapping from AuthContext fields to gateway header names */
|
|
269
|
+
readonly headerMapping: GatewayHeaderMapping;
|
|
270
|
+
/** Trust verification: check that request came from a trusted gateway */
|
|
271
|
+
readonly trustSource: {
|
|
272
|
+
/** Header set by the gateway to prove trust */
|
|
273
|
+
readonly header: string;
|
|
274
|
+
/** Accepted values for the trust header */
|
|
275
|
+
readonly expectedValues: string[];
|
|
276
|
+
};
|
|
277
|
+
/** Headers to strip from the request after extraction (prevent spoofing) */
|
|
278
|
+
readonly stripHeaders?: string[] | undefined;
|
|
279
|
+
/** Methods to skip authentication for */
|
|
280
|
+
readonly skipMethods?: string[] | undefined;
|
|
281
|
+
/** Propagate auth context as headers for downstream services */
|
|
282
|
+
readonly propagateHeaders?: boolean | undefined;
|
|
283
|
+
/** Default credential type when not provided by gateway */
|
|
284
|
+
readonly defaultType?: string | undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Session-based auth interceptor options.
|
|
289
|
+
*
|
|
290
|
+
* Two-step authentication: verify session token, then map session data to AuthContext.
|
|
291
|
+
*/
|
|
292
|
+
export interface SessionAuthInterceptorOptions {
|
|
293
|
+
/**
|
|
294
|
+
* Verify session token and return raw session data.
|
|
295
|
+
* Must throw on invalid/expired sessions.
|
|
296
|
+
*
|
|
297
|
+
* @param token - Session token string
|
|
298
|
+
* @param headers - Request headers (for additional context)
|
|
299
|
+
* @returns Raw session data
|
|
300
|
+
*/
|
|
301
|
+
readonly verifySession: (token: string, headers: Headers) => unknown | Promise<unknown>;
|
|
302
|
+
/**
|
|
303
|
+
* Map raw session data to AuthContext.
|
|
304
|
+
*
|
|
305
|
+
* @param session - Raw session data from verifySession
|
|
306
|
+
* @returns Normalized auth context
|
|
307
|
+
*/
|
|
308
|
+
readonly mapSession: (session: unknown) => AuthContext | Promise<AuthContext>;
|
|
309
|
+
/**
|
|
310
|
+
* Custom token extraction.
|
|
311
|
+
* Default: extracts Bearer token from Authorization header.
|
|
312
|
+
*/
|
|
313
|
+
readonly extractToken?: ((req: { header: Headers }) => string | null | Promise<string | null>) | undefined;
|
|
314
|
+
/** LRU cache for session verification results */
|
|
315
|
+
readonly cache?: CacheOptions | undefined;
|
|
316
|
+
/** Methods to skip authentication for */
|
|
317
|
+
readonly skipMethods?: string[] | undefined;
|
|
318
|
+
/** Propagate auth context as headers for downstream services */
|
|
319
|
+
readonly propagateHeaders?: boolean | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* Filter which claims are propagated in headers.
|
|
322
|
+
* When set, only listed claim keys are included in x-auth-claims header.
|
|
323
|
+
* When not set, all claims are propagated.
|
|
324
|
+
*/
|
|
325
|
+
readonly propagatedClaims?: string[] | undefined;
|
|
326
|
+
}
|