@prmichaelsen/mcp-auth 0.1.0 → 0.1.1
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 +220 -75
- package/dist/auth/index.js +7 -1
- package/dist/auth/index.js.map +2 -2
- package/dist/auth/providers/api-token-resolver.js +225 -0
- package/dist/auth/providers/api-token-resolver.js.map +7 -0
- package/dist/auth/providers/index.js +12 -0
- package/dist/auth/providers/index.js.map +2 -2
- package/dist/auth/providers/jwt-provider.js +136 -0
- package/dist/auth/providers/jwt-provider.js.map +7 -0
- package/dist/auth/providers/jwt-token-resolver.js +94 -0
- package/dist/auth/providers/jwt-token-resolver.js.map +7 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +2 -2
- package/dist/wrapper/server-wrapper.js.map +2 -2
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,42 +6,60 @@ Authentication and multi-tenancy framework for MCP (Model Context Protocol) serv
|
|
|
6
6
|
|
|
7
7
|
`@prmichaelsen/mcp-auth` provides a pluggable authentication system for MCP servers, enabling:
|
|
8
8
|
|
|
9
|
+
- **Zero modification**: Wrap existing MCP servers without code changes
|
|
9
10
|
- **Multi-tenancy**: Multiple users with separate resource tokens
|
|
10
|
-
- **Auth-agnostic**: Support for JWT,
|
|
11
|
+
- **Auth-agnostic**: Support for JWT, environment variables, or custom auth schemes
|
|
11
12
|
- **Transport-agnostic**: Works with stdio, HTTP, and SSE transports
|
|
12
13
|
- **Type-safe**: Full TypeScript support
|
|
13
|
-
- **Composable**: Middleware for rate limiting, logging,
|
|
14
|
+
- **Composable**: Middleware for rate limiting, logging, timeouts, retries
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Two Patterns
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
### Pattern 1: Server Wrapping (Recommended for Production)
|
|
19
|
+
|
|
20
|
+
Wrap existing MCP servers without modification:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { wrapServer, JWTAuthProvider, APITokenResolver } from '@prmichaelsen/mcp-auth';
|
|
24
|
+
import { createServer } from '@myorg/my-mcp-server';
|
|
25
|
+
|
|
26
|
+
const wrapped = wrapServer({
|
|
27
|
+
serverFactory: createServer,
|
|
28
|
+
|
|
29
|
+
// Validates JWT from tenant manager
|
|
30
|
+
authProvider: new JWTAuthProvider({
|
|
31
|
+
jwtSecret: process.env.JWT_SECRET
|
|
32
|
+
}),
|
|
33
|
+
|
|
34
|
+
// Resolves tokens via tenant manager API (recommended)
|
|
35
|
+
tokenResolver: new APITokenResolver({
|
|
36
|
+
tenantManagerUrl: process.env.TENANT_MANAGER_URL,
|
|
37
|
+
serviceToken: process.env.SERVICE_TOKEN
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
resourceType: 'myapi',
|
|
41
|
+
transport: { type: 'sse', port: 3000 }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await wrapped.start();
|
|
19
45
|
```
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
### Pattern 2: Tool-Level Auth
|
|
22
48
|
|
|
23
|
-
|
|
49
|
+
Build new servers with integrated authentication:
|
|
24
50
|
|
|
25
51
|
```typescript
|
|
26
|
-
import { withAuth,
|
|
27
|
-
import { EnvAuthProvider, SimpleTokenResolver } from '@prmichaelsen/mcp-auth/providers/env';
|
|
28
|
-
|
|
29
|
-
// Setup auth provider
|
|
30
|
-
const authProvider = new EnvAuthProvider();
|
|
31
|
-
const tokenResolver = new SimpleTokenResolver({ tokenEnvVar: 'API_TOKEN' });
|
|
52
|
+
import { AuthenticatedMCPServer, withAuth, EnvAuthProvider, SimpleTokenResolver } from '@prmichaelsen/mcp-auth';
|
|
32
53
|
|
|
33
|
-
// Create server
|
|
34
54
|
const server = new AuthenticatedMCPServer({
|
|
35
|
-
name: 'my-
|
|
36
|
-
authProvider,
|
|
37
|
-
tokenResolver,
|
|
55
|
+
name: 'my-server',
|
|
56
|
+
authProvider: new EnvAuthProvider(),
|
|
57
|
+
tokenResolver: new SimpleTokenResolver({ tokenEnvVar: 'API_TOKEN' }),
|
|
38
58
|
resourceType: 'myapi',
|
|
39
59
|
transport: { type: 'stdio' }
|
|
40
60
|
});
|
|
41
61
|
|
|
42
|
-
// Register tool with automatic auth
|
|
43
62
|
server.registerTool('get_data', withAuth(async (args, accessToken, userId) => {
|
|
44
|
-
// accessToken and userId are automatically injected
|
|
45
63
|
const client = new MyAPIClient(accessToken);
|
|
46
64
|
return client.getData(args);
|
|
47
65
|
}));
|
|
@@ -49,77 +67,123 @@ server.registerTool('get_data', withAuth(async (args, accessToken, userId) => {
|
|
|
49
67
|
await server.start();
|
|
50
68
|
```
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
## Installation
|
|
53
71
|
|
|
54
|
-
```
|
|
55
|
-
|
|
72
|
+
```bash
|
|
73
|
+
# Core package
|
|
74
|
+
npm install @prmichaelsen/mcp-auth @modelcontextprotocol/sdk
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
description = 'Fetch data from API';
|
|
60
|
-
|
|
61
|
-
async execute(args, accessToken, userId) {
|
|
62
|
-
const client = new MyAPIClient(accessToken);
|
|
63
|
-
return client.getData(args);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
76
|
+
# For JWT support
|
|
77
|
+
npm install jsonwebtoken
|
|
66
78
|
|
|
67
|
-
|
|
79
|
+
# For SSE/HTTP transports
|
|
80
|
+
npm install express cors
|
|
68
81
|
```
|
|
69
82
|
|
|
70
83
|
## Authentication Providers
|
|
71
84
|
|
|
72
|
-
###
|
|
85
|
+
### EnvAuthProvider (Single-User)
|
|
86
|
+
|
|
87
|
+
For local development and single-user scenarios:
|
|
73
88
|
|
|
74
89
|
```typescript
|
|
75
|
-
import { EnvAuthProvider, SimpleTokenResolver } from '@prmichaelsen/mcp-auth
|
|
90
|
+
import { EnvAuthProvider, SimpleTokenResolver } from '@prmichaelsen/mcp-auth';
|
|
76
91
|
|
|
77
|
-
const authProvider = new EnvAuthProvider(
|
|
78
|
-
|
|
92
|
+
const authProvider = new EnvAuthProvider({
|
|
93
|
+
userIdEnvVar: 'MCP_USER_ID',
|
|
94
|
+
defaultUserId: 'local-user'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const tokenResolver = new SimpleTokenResolver({
|
|
98
|
+
tokenEnvVar: 'API_TOKEN'
|
|
99
|
+
});
|
|
79
100
|
```
|
|
80
101
|
|
|
81
|
-
###
|
|
102
|
+
### JWTAuthProvider + APITokenResolver (Multi-Tenant Production) ⭐ RECOMMENDED
|
|
103
|
+
|
|
104
|
+
For production multi-tenant deployments:
|
|
82
105
|
|
|
83
106
|
```typescript
|
|
84
|
-
import { JWTAuthProvider } from '@prmichaelsen/mcp-auth
|
|
107
|
+
import { JWTAuthProvider, APITokenResolver } from '@prmichaelsen/mcp-auth';
|
|
85
108
|
|
|
109
|
+
// Validates JWT tokens from tenant manager
|
|
86
110
|
const authProvider = new JWTAuthProvider({
|
|
87
111
|
jwtSecret: process.env.JWT_SECRET,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
userIdClaim: 'sub'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Resolves tokens via tenant manager API
|
|
116
|
+
const tokenResolver = new APITokenResolver({
|
|
117
|
+
tenantManagerUrl: 'https://tenant-manager.example.com',
|
|
118
|
+
serviceToken: process.env.SERVICE_TOKEN,
|
|
119
|
+
cacheTokens: true, // Cache for performance
|
|
120
|
+
cacheTtl: 300000 // 5 minutes
|
|
92
121
|
});
|
|
93
122
|
```
|
|
94
123
|
|
|
95
|
-
|
|
124
|
+
**Why API-Based is Better:**
|
|
125
|
+
- ✅ Automatic token refresh (no new JWT needed)
|
|
126
|
+
- ✅ Immediate token revocation
|
|
127
|
+
- ✅ Tokens not exposed in JWT
|
|
128
|
+
- ✅ Small JWT size (~200 bytes)
|
|
129
|
+
- ✅ Centralized token management
|
|
130
|
+
|
|
131
|
+
### JWTAuthProvider + JWTTokenResolver (MVP/Prototyping)
|
|
132
|
+
|
|
133
|
+
For quick setup with JWT-embedded tokens:
|
|
96
134
|
|
|
97
135
|
```typescript
|
|
98
|
-
import {
|
|
136
|
+
import { JWTAuthProvider, JWTTokenResolver } from '@prmichaelsen/mcp-auth';
|
|
99
137
|
|
|
100
|
-
const authProvider = new
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
const authProvider = new JWTAuthProvider({
|
|
139
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
140
|
+
extractTokens: true // Extract tokens from JWT
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const tokenResolver = new JWTTokenResolver({ authProvider });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Trade-offs:**
|
|
147
|
+
- ✅ Zero API calls (faster)
|
|
148
|
+
- ✅ Simpler to implement
|
|
149
|
+
- ❌ Token rotation requires new JWT
|
|
150
|
+
- ❌ Larger JWT size
|
|
151
|
+
- ❌ Tokens exposed in JWT payload
|
|
152
|
+
|
|
153
|
+
### APITokenResolver (API-Based)
|
|
154
|
+
|
|
155
|
+
For resolving tokens via tenant manager API:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { JWTAuthProvider, APITokenResolver } from '@prmichaelsen/mcp-auth';
|
|
159
|
+
|
|
160
|
+
const authProvider = new JWTAuthProvider({
|
|
161
|
+
jwtSecret: process.env.JWT_SECRET
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const tokenResolver = new APITokenResolver({
|
|
165
|
+
tenantManagerUrl: 'https://tenant-manager.example.com',
|
|
166
|
+
serviceToken: process.env.SERVICE_TOKEN,
|
|
167
|
+
endpointPath: '/api/credentials/:userId/:resourceType'
|
|
105
168
|
});
|
|
106
169
|
```
|
|
107
170
|
|
|
108
171
|
### Custom Provider
|
|
109
172
|
|
|
173
|
+
Implement your own authentication logic:
|
|
174
|
+
|
|
110
175
|
```typescript
|
|
111
176
|
import { AuthProvider, AuthResult, RequestContext } from '@prmichaelsen/mcp-auth';
|
|
112
177
|
|
|
113
178
|
class CustomAuthProvider implements AuthProvider {
|
|
114
179
|
async authenticate(context: RequestContext): Promise<AuthResult> {
|
|
115
|
-
// Your custom auth logic
|
|
116
180
|
const apiKey = context.headers?.['x-api-key'];
|
|
117
181
|
|
|
118
182
|
if (!apiKey) {
|
|
119
183
|
return { authenticated: false, error: 'No API key' };
|
|
120
184
|
}
|
|
121
185
|
|
|
122
|
-
//
|
|
186
|
+
// Your validation logic
|
|
123
187
|
return {
|
|
124
188
|
authenticated: true,
|
|
125
189
|
userId: 'user-123'
|
|
@@ -131,11 +195,12 @@ class CustomAuthProvider implements AuthProvider {
|
|
|
131
195
|
## Middleware Composition
|
|
132
196
|
|
|
133
197
|
```typescript
|
|
134
|
-
import { compose, withAuth, withRateLimit, withLogging } from '@prmichaelsen/mcp-auth';
|
|
198
|
+
import { compose, withAuth, withRateLimit, withLogging, withTimeout } from '@prmichaelsen/mcp-auth';
|
|
135
199
|
|
|
136
200
|
const getTool = compose(
|
|
137
|
-
withLogging(),
|
|
201
|
+
withLogging({ logArgs: true }),
|
|
138
202
|
withRateLimit({ maxRequests: 100, windowMs: 60000 }),
|
|
203
|
+
withTimeout(5000),
|
|
139
204
|
withAuth(),
|
|
140
205
|
async (args, accessToken, userId) => {
|
|
141
206
|
// Your tool logic
|
|
@@ -150,41 +215,121 @@ server.registerTool('get_data', getTool);
|
|
|
150
215
|
### Stdio (Local)
|
|
151
216
|
|
|
152
217
|
```typescript
|
|
153
|
-
|
|
154
|
-
// ...
|
|
155
|
-
transport: { type: 'stdio' }
|
|
156
|
-
});
|
|
218
|
+
transport: { type: 'stdio' }
|
|
157
219
|
```
|
|
158
220
|
|
|
159
|
-
###
|
|
221
|
+
### SSE (Remote Multi-Tenant)
|
|
160
222
|
|
|
161
223
|
```typescript
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
224
|
+
transport: {
|
|
225
|
+
type: 'sse',
|
|
226
|
+
port: 3000,
|
|
227
|
+
host: '0.0.0.0',
|
|
228
|
+
basePath: '/mcp',
|
|
229
|
+
cors: true
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### HTTP (Remote)
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
transport: {
|
|
237
|
+
type: 'http',
|
|
238
|
+
port: 3000,
|
|
239
|
+
host: '0.0.0.0'
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## MCP Server Contract
|
|
244
|
+
|
|
245
|
+
To make your MCP server compatible with `wrapServer()`, export a factory function:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
249
|
+
|
|
250
|
+
export function createServer(accessToken: string, userId?: string): Server {
|
|
251
|
+
const server = new Server({ name: 'my-server', version: '1.0.0' });
|
|
252
|
+
const client = new MyAPIClient(accessToken);
|
|
253
|
+
|
|
254
|
+
// Register your tool handlers...
|
|
255
|
+
|
|
256
|
+
return server;
|
|
257
|
+
}
|
|
171
258
|
```
|
|
172
259
|
|
|
260
|
+
That's it! No mcp-auth imports needed in your server.
|
|
261
|
+
|
|
262
|
+
## Architecture
|
|
263
|
+
|
|
264
|
+
### Three-Tier Deployment
|
|
265
|
+
|
|
266
|
+
1. **Chat Platform** - Sends MCP requests with JWT
|
|
267
|
+
2. **Tenant Manager** - Issues JWTs, manages user credentials
|
|
268
|
+
3. **MCP Server Instance** - Uses mcp-auth to wrap MCP servers
|
|
269
|
+
|
|
270
|
+
### Token Resolution Approaches
|
|
271
|
+
|
|
272
|
+
**Approach 1: JWT with Embedded Tokens** (Recommended)
|
|
273
|
+
- Tenant manager includes resource tokens in JWT
|
|
274
|
+
- Zero external calls
|
|
275
|
+
- Fastest performance
|
|
276
|
+
|
|
277
|
+
**Approach 2: API-Based Resolution**
|
|
278
|
+
- Tenant manager provides API for token lookup
|
|
279
|
+
- Better separation of concerns
|
|
280
|
+
- Easier token rotation
|
|
281
|
+
|
|
282
|
+
See [`agent/token-resolution-approaches.md`](./agent/token-resolution-approaches.md) for details.
|
|
283
|
+
|
|
173
284
|
## Documentation
|
|
174
285
|
|
|
175
|
-
|
|
286
|
+
Comprehensive architecture documentation in [`agent/`](./agent/):
|
|
176
287
|
|
|
177
|
-
- [`
|
|
178
|
-
- [`
|
|
179
|
-
- [`
|
|
288
|
+
- [`server-wrapping-pattern.md`](./agent/server-wrapping-pattern.md) - Server wrapping architecture
|
|
289
|
+
- [`server-contract.md`](./agent/server-contract.md) - MCP server compatibility guide
|
|
290
|
+
- [`dual-pattern-architecture.md`](./agent/dual-pattern-architecture.md) - Both patterns explained
|
|
291
|
+
- [`token-resolution-approaches.md`](./agent/token-resolution-approaches.md) - Token resolution strategies
|
|
292
|
+
- [`why-two-steps.md`](./agent/why-two-steps.md) - AuthProvider vs TokenResolver
|
|
293
|
+
- [`INTEGRATION.md`](./INTEGRATION.md) - Integration guide for MCP server authors
|
|
180
294
|
|
|
181
295
|
## Examples
|
|
182
296
|
|
|
183
|
-
|
|
297
|
+
Working examples coming soon in [`examples/`](./examples/):
|
|
184
298
|
|
|
185
299
|
- `simple-stdio/` - Single-user stdio server
|
|
186
|
-
- `
|
|
187
|
-
- `
|
|
300
|
+
- `wrapped-server/` - Server wrapping with JWT
|
|
301
|
+
- `tool-level-auth/` - Tool-level authentication
|
|
302
|
+
- `jwt-multi-tenant/` - Multi-tenant JWT deployment
|
|
303
|
+
|
|
304
|
+
## API Reference
|
|
305
|
+
|
|
306
|
+
### Core Functions
|
|
307
|
+
|
|
308
|
+
- `wrapServer(config)` - Wrap MCP server with authentication
|
|
309
|
+
- `withAuth(handler)` - Add auth to function-based tools
|
|
310
|
+
- `compose(...middlewares)` - Compose middleware functions
|
|
311
|
+
|
|
312
|
+
### Classes
|
|
313
|
+
|
|
314
|
+
- `AuthenticatedMCPServer` - MCP server with integrated auth
|
|
315
|
+
- `AuthenticatedTool` - Wrapper for class-based tools
|
|
316
|
+
- `BaseAuthProvider` - Base class for auth providers
|
|
317
|
+
|
|
318
|
+
### Providers
|
|
319
|
+
|
|
320
|
+
- `EnvAuthProvider` - Environment variable authentication
|
|
321
|
+
- `SimpleTokenResolver` - Environment variable token resolution
|
|
322
|
+
- `JWTAuthProvider` - JWT validation with token extraction
|
|
323
|
+
- `JWTTokenResolver` - JWT-embedded token resolution
|
|
324
|
+
- `APITokenResolver` - API-based token resolution
|
|
325
|
+
|
|
326
|
+
### Middleware
|
|
327
|
+
|
|
328
|
+
- `withAuth()` - Authentication
|
|
329
|
+
- `withLogging()` - Request/response logging
|
|
330
|
+
- `withRateLimit()` - Rate limiting per user
|
|
331
|
+
- `withTimeout()` - Request timeout
|
|
332
|
+
- `withRetry()` - Automatic retry on failure
|
|
188
333
|
|
|
189
334
|
## License
|
|
190
335
|
|
|
@@ -192,4 +337,4 @@ MIT
|
|
|
192
337
|
|
|
193
338
|
## Contributing
|
|
194
339
|
|
|
195
|
-
Contributions welcome! Please
|
|
340
|
+
Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/prmichaelsen/mcp-auth).
|
package/dist/auth/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { BaseAuthProvider } from "./base-provider.js";
|
|
2
2
|
import {
|
|
3
3
|
EnvAuthProvider,
|
|
4
|
-
SimpleTokenResolver
|
|
4
|
+
SimpleTokenResolver,
|
|
5
|
+
JWTAuthProvider,
|
|
6
|
+
JWTTokenResolver,
|
|
7
|
+
APITokenResolver
|
|
5
8
|
} from "./providers/index.js";
|
|
6
9
|
export {
|
|
10
|
+
APITokenResolver,
|
|
7
11
|
BaseAuthProvider,
|
|
8
12
|
EnvAuthProvider,
|
|
13
|
+
JWTAuthProvider,
|
|
14
|
+
JWTTokenResolver,
|
|
9
15
|
SimpleTokenResolver
|
|
10
16
|
};
|
|
11
17
|
//# sourceMappingURL=index.js.map
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/auth/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Authentication module exports\n */\n\n// Types\nexport type {\n AuthProvider,\n ResourceTokenResolver,\n AuthenticatedContext,\n AuthProviderConfig,\n TokenResolverConfig\n} from './types.js';\n\n// Base provider\nexport { BaseAuthProvider } from './base-provider.js';\n\n// Providers\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig,\n SimpleTokenResolver,\n type SimpleTokenResolverConfig\n} from './providers/index.js';\n"],
|
|
5
|
-
"mappings": "AAcA,SAAS,wBAAwB;AAGjC;AAAA,EACE;AAAA,EAEA;AAAA,OAEK;",
|
|
4
|
+
"sourcesContent": ["/**\n * Authentication module exports\n */\n\n// Types\nexport type {\n AuthProvider,\n ResourceTokenResolver,\n AuthenticatedContext,\n AuthProviderConfig,\n TokenResolverConfig\n} from './types.js';\n\n// Base provider\nexport { BaseAuthProvider } from './base-provider.js';\n\n// Providers\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig,\n SimpleTokenResolver,\n type SimpleTokenResolverConfig,\n JWTAuthProvider,\n type JWTAuthProviderConfig,\n type JWTPayload,\n JWTTokenResolver,\n type JWTTokenResolverConfig,\n APITokenResolver,\n type APITokenResolverConfig\n} from './providers/index.js';\n"],
|
|
5
|
+
"mappings": "AAcA,SAAS,wBAAwB;AAGjC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAGA;AAAA,EAEA;AAAA,OAEK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { TokenResolutionError } from "../../utils/errors.js";
|
|
2
|
+
import { createLogger } from "../../utils/logger.js";
|
|
3
|
+
import { validateAccessToken } from "../../utils/validation.js";
|
|
4
|
+
class APITokenResolver {
|
|
5
|
+
config;
|
|
6
|
+
logger;
|
|
7
|
+
tokenCache;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
if (!config.tenantManagerUrl) {
|
|
10
|
+
throw new Error("tenantManagerUrl is required for APITokenResolver");
|
|
11
|
+
}
|
|
12
|
+
if (!config.serviceToken) {
|
|
13
|
+
throw new Error("serviceToken is required for APITokenResolver");
|
|
14
|
+
}
|
|
15
|
+
this.config = {
|
|
16
|
+
tenantManagerUrl: config.tenantManagerUrl.replace(/\/$/, ""),
|
|
17
|
+
// Remove trailing slash
|
|
18
|
+
serviceToken: config.serviceToken,
|
|
19
|
+
endpointPath: config.endpointPath ?? "/api/credentials/:userId/:resourceType",
|
|
20
|
+
timeoutMs: config.timeoutMs ?? 5e3,
|
|
21
|
+
throwOnMissing: config.throwOnMissing ?? true,
|
|
22
|
+
validateToken: config.validateToken ?? true,
|
|
23
|
+
customHeaders: config.customHeaders ?? {},
|
|
24
|
+
cacheTokens: config.cacheTokens ?? true,
|
|
25
|
+
cacheTtl: config.cacheTtl ?? 3e5,
|
|
26
|
+
// 5 minutes
|
|
27
|
+
autoRefresh: config.autoRefresh ?? false
|
|
28
|
+
};
|
|
29
|
+
this.logger = createLogger({ enabled: true, level: "info" });
|
|
30
|
+
this.tokenCache = /* @__PURE__ */ new Map();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve token by calling tenant manager API
|
|
34
|
+
*/
|
|
35
|
+
async resolveToken(userId, resourceType) {
|
|
36
|
+
if (this.config.cacheTokens) {
|
|
37
|
+
const cacheKey = `${userId}:${resourceType}`;
|
|
38
|
+
const cached = this.tokenCache.get(cacheKey);
|
|
39
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
40
|
+
this.logger.debug("Token retrieved from cache", {
|
|
41
|
+
userId,
|
|
42
|
+
resourceType,
|
|
43
|
+
cacheKey
|
|
44
|
+
});
|
|
45
|
+
return cached.token;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const url = this.buildUrl(userId, resourceType);
|
|
49
|
+
try {
|
|
50
|
+
this.logger.debug("Calling tenant manager API", {
|
|
51
|
+
userId,
|
|
52
|
+
resourceType,
|
|
53
|
+
url
|
|
54
|
+
});
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
method: "GET",
|
|
59
|
+
headers: {
|
|
60
|
+
"Authorization": `Bearer ${this.config.serviceToken}`,
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
...this.config.customHeaders
|
|
63
|
+
},
|
|
64
|
+
signal: controller.signal
|
|
65
|
+
});
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
69
|
+
this.logger.warn("API request failed", {
|
|
70
|
+
userId,
|
|
71
|
+
resourceType,
|
|
72
|
+
status: response.status,
|
|
73
|
+
error: errorText
|
|
74
|
+
});
|
|
75
|
+
if (this.config.throwOnMissing) {
|
|
76
|
+
throw new TokenResolutionError(userId, resourceType, {
|
|
77
|
+
status: response.status,
|
|
78
|
+
error: errorText,
|
|
79
|
+
url
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
const token = data.accessToken || data.access_token || data.token;
|
|
86
|
+
if (!token) {
|
|
87
|
+
this.logger.warn("API response missing token", {
|
|
88
|
+
userId,
|
|
89
|
+
resourceType,
|
|
90
|
+
responseKeys: Object.keys(data || {})
|
|
91
|
+
});
|
|
92
|
+
if (this.config.throwOnMissing) {
|
|
93
|
+
throw new TokenResolutionError(userId, resourceType, {
|
|
94
|
+
reason: "API response missing accessToken field"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (this.config.validateToken) {
|
|
100
|
+
try {
|
|
101
|
+
validateAccessToken(token);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
this.logger.error("Token validation failed", error, {
|
|
104
|
+
userId,
|
|
105
|
+
resourceType
|
|
106
|
+
});
|
|
107
|
+
if (this.config.throwOnMissing) {
|
|
108
|
+
throw new TokenResolutionError(userId, resourceType, {
|
|
109
|
+
reason: "Invalid token format"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (this.config.cacheTokens) {
|
|
116
|
+
const cacheKey = `${userId}:${resourceType}`;
|
|
117
|
+
this.tokenCache.set(cacheKey, {
|
|
118
|
+
token,
|
|
119
|
+
expiresAt: Date.now() + this.config.cacheTtl
|
|
120
|
+
});
|
|
121
|
+
this.logger.debug("Token cached", {
|
|
122
|
+
userId,
|
|
123
|
+
resourceType,
|
|
124
|
+
cacheKey,
|
|
125
|
+
ttl: this.config.cacheTtl
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
this.logger.info("Token resolved successfully", {
|
|
129
|
+
userId,
|
|
130
|
+
resourceType,
|
|
131
|
+
tokenLength: token.length
|
|
132
|
+
});
|
|
133
|
+
return token;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof TokenResolutionError) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
this.logger.error("API request error", error, {
|
|
139
|
+
userId,
|
|
140
|
+
resourceType,
|
|
141
|
+
url
|
|
142
|
+
});
|
|
143
|
+
if (this.config.throwOnMissing) {
|
|
144
|
+
throw new TokenResolutionError(userId, resourceType, {
|
|
145
|
+
reason: error instanceof Error ? error.message : "API request failed",
|
|
146
|
+
url
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Build API URL from template
|
|
154
|
+
*/
|
|
155
|
+
buildUrl(userId, resourceType) {
|
|
156
|
+
const path = this.config.endpointPath.replace(":userId", encodeURIComponent(userId)).replace(":resourceType", encodeURIComponent(resourceType));
|
|
157
|
+
return `${this.config.tenantManagerUrl}${path}`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Refresh token (calls API again)
|
|
161
|
+
*/
|
|
162
|
+
async refreshToken(userId, resourceType) {
|
|
163
|
+
this.logger.info("Refreshing token via API", { userId, resourceType });
|
|
164
|
+
const cacheKey = `${userId}:${resourceType}`;
|
|
165
|
+
this.tokenCache.delete(cacheKey);
|
|
166
|
+
return this.resolveToken(userId, resourceType);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Validate token (basic check)
|
|
170
|
+
*/
|
|
171
|
+
async validateToken(token, resourceType) {
|
|
172
|
+
if (!this.config.validateToken) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
validateAccessToken(token);
|
|
177
|
+
return true;
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Initialize resolver
|
|
184
|
+
*/
|
|
185
|
+
async initialize() {
|
|
186
|
+
this.logger.info("APITokenResolver initialized", {
|
|
187
|
+
tenantManagerUrl: this.config.tenantManagerUrl,
|
|
188
|
+
endpointPath: this.config.endpointPath,
|
|
189
|
+
cacheEnabled: this.config.cacheTokens,
|
|
190
|
+
timeoutMs: this.config.timeoutMs
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
new URL(this.config.tenantManagerUrl);
|
|
194
|
+
} catch {
|
|
195
|
+
throw new Error(`Invalid tenantManagerUrl: ${this.config.tenantManagerUrl}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Cleanup resources
|
|
200
|
+
*/
|
|
201
|
+
async cleanup() {
|
|
202
|
+
this.tokenCache.clear();
|
|
203
|
+
this.logger.info("APITokenResolver cleaned up");
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Clear token cache
|
|
207
|
+
*/
|
|
208
|
+
clearCache() {
|
|
209
|
+
this.tokenCache.clear();
|
|
210
|
+
this.logger.debug("Token cache cleared");
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get cache statistics
|
|
214
|
+
*/
|
|
215
|
+
getCacheStats() {
|
|
216
|
+
return {
|
|
217
|
+
size: this.tokenCache.size,
|
|
218
|
+
keys: Array.from(this.tokenCache.keys())
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export {
|
|
223
|
+
APITokenResolver
|
|
224
|
+
};
|
|
225
|
+
//# sourceMappingURL=api-token-resolver.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/auth/providers/api-token-resolver.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * API-based token resolver\n * \n * Resolves tokens by calling the tenant manager's API.\n * Alternative to JWT-embedded tokens for better separation.\n */\n\nimport type { ResourceTokenResolver, TokenResolverConfig } from '../types.js';\nimport { TokenResolutionError } from '../../utils/errors.js';\nimport { createLogger, type Logger } from '../../utils/logger.js';\nimport { validateAccessToken } from '../../utils/validation.js';\n\n/**\n * Configuration for APITokenResolver\n */\nexport interface APITokenResolverConfig extends TokenResolverConfig {\n /**\n * Tenant manager API base URL\n * @example 'https://tenant-manager.example.com'\n */\n tenantManagerUrl: string;\n \n /**\n * Service token for authenticating MCP server \u2192 tenant manager requests\n * This is a separate token from user JWTs\n */\n serviceToken: string;\n \n /**\n * API endpoint path template\n * @default '/api/credentials/:userId/:resourceType'\n * \n * Variables:\n * - :userId - Will be replaced with actual user ID\n * - :resourceType - Will be replaced with resource type\n */\n endpointPath?: string;\n \n /**\n * Request timeout in milliseconds\n * @default 5000\n */\n timeoutMs?: number;\n \n /**\n * Whether to throw error if token is not found\n * @default true\n */\n throwOnMissing?: boolean;\n \n /**\n * Whether to validate token format\n * @default true\n */\n validateToken?: boolean;\n \n /**\n * Custom headers to include in API requests\n */\n customHeaders?: Record<string, string>;\n}\n\n/**\n * API token resolver\n * \n * Resolves tokens by calling the tenant manager's API endpoint.\n * This approach provides better separation between MCP server and tenant manager.\n * \n * @example\n * ```typescript\n * const resolver = new APITokenResolver({\n * tenantManagerUrl: 'https://tenant-manager.example.com',\n * serviceToken: process.env.SERVICE_TOKEN\n * });\n * \n * // Calls: GET https://tenant-manager.example.com/api/credentials/user-123/instagram\n * // Headers: { Authorization: Bearer <service-token> }\n * // Returns: { accessToken: \"IGQVJXabc...\" }\n * ```\n */\nexport class APITokenResolver implements ResourceTokenResolver {\n private config: Required<APITokenResolverConfig>;\n private logger: Logger;\n private tokenCache: Map<string, { token: string; expiresAt: number }>;\n \n constructor(config: APITokenResolverConfig) {\n if (!config.tenantManagerUrl) {\n throw new Error('tenantManagerUrl is required for APITokenResolver');\n }\n if (!config.serviceToken) {\n throw new Error('serviceToken is required for APITokenResolver');\n }\n \n this.config = {\n tenantManagerUrl: config.tenantManagerUrl.replace(/\\/$/, ''), // Remove trailing slash\n serviceToken: config.serviceToken,\n endpointPath: config.endpointPath ?? '/api/credentials/:userId/:resourceType',\n timeoutMs: config.timeoutMs ?? 5000,\n throwOnMissing: config.throwOnMissing ?? true,\n validateToken: config.validateToken ?? true,\n customHeaders: config.customHeaders ?? {},\n cacheTokens: config.cacheTokens ?? true,\n cacheTtl: config.cacheTtl ?? 300000, // 5 minutes\n autoRefresh: config.autoRefresh ?? false\n };\n \n this.logger = createLogger({ enabled: true, level: 'info' });\n this.tokenCache = new Map();\n }\n \n /**\n * Resolve token by calling tenant manager API\n */\n async resolveToken(userId: string, resourceType: string): Promise<string | null> {\n // Check cache if enabled\n if (this.config.cacheTokens) {\n const cacheKey = `${userId}:${resourceType}`;\n const cached = this.tokenCache.get(cacheKey);\n \n if (cached && cached.expiresAt > Date.now()) {\n this.logger.debug('Token retrieved from cache', {\n userId,\n resourceType,\n cacheKey\n });\n return cached.token;\n }\n }\n \n // Build API URL\n const url = this.buildUrl(userId, resourceType);\n \n try {\n this.logger.debug('Calling tenant manager API', {\n userId,\n resourceType,\n url\n });\n \n // Call tenant manager API with timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n \n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.config.serviceToken}`,\n 'Content-Type': 'application/json',\n ...this.config.customHeaders\n },\n signal: controller.signal\n });\n \n clearTimeout(timeoutId);\n \n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n \n this.logger.warn('API request failed', {\n userId,\n resourceType,\n status: response.status,\n error: errorText\n });\n \n if (this.config.throwOnMissing) {\n throw new TokenResolutionError(userId, resourceType, {\n status: response.status,\n error: errorText,\n url\n });\n }\n \n return null;\n }\n \n // Parse response\n const data = await response.json() as any;\n const token = data.accessToken || data.access_token || data.token;\n \n if (!token) {\n this.logger.warn('API response missing token', {\n userId,\n resourceType,\n responseKeys: Object.keys(data || {})\n });\n \n if (this.config.throwOnMissing) {\n throw new TokenResolutionError(userId, resourceType, {\n reason: 'API response missing accessToken field'\n });\n }\n \n return null;\n }\n \n // Validate token format if enabled\n if (this.config.validateToken) {\n try {\n validateAccessToken(token);\n } catch (error) {\n this.logger.error('Token validation failed', error as Error, {\n userId,\n resourceType\n });\n \n if (this.config.throwOnMissing) {\n throw new TokenResolutionError(userId, resourceType, {\n reason: 'Invalid token format'\n });\n }\n \n return null;\n }\n }\n \n // Cache token if enabled\n if (this.config.cacheTokens) {\n const cacheKey = `${userId}:${resourceType}`;\n this.tokenCache.set(cacheKey, {\n token,\n expiresAt: Date.now() + this.config.cacheTtl\n });\n \n this.logger.debug('Token cached', {\n userId,\n resourceType,\n cacheKey,\n ttl: this.config.cacheTtl\n });\n }\n \n this.logger.info('Token resolved successfully', {\n userId,\n resourceType,\n tokenLength: token.length\n });\n \n return token;\n \n } catch (error) {\n if (error instanceof TokenResolutionError) {\n throw error;\n }\n \n this.logger.error('API request error', error as Error, {\n userId,\n resourceType,\n url\n });\n \n if (this.config.throwOnMissing) {\n throw new TokenResolutionError(userId, resourceType, {\n reason: error instanceof Error ? error.message : 'API request failed',\n url\n });\n }\n \n return null;\n }\n }\n \n /**\n * Build API URL from template\n */\n private buildUrl(userId: string, resourceType: string): string {\n const path = this.config.endpointPath\n .replace(':userId', encodeURIComponent(userId))\n .replace(':resourceType', encodeURIComponent(resourceType));\n \n return `${this.config.tenantManagerUrl}${path}`;\n }\n \n /**\n * Refresh token (calls API again)\n */\n async refreshToken(userId: string, resourceType: string): Promise<string | null> {\n this.logger.info('Refreshing token via API', { userId, resourceType });\n \n // Clear cache and fetch fresh token\n const cacheKey = `${userId}:${resourceType}`;\n this.tokenCache.delete(cacheKey);\n \n return this.resolveToken(userId, resourceType);\n }\n \n /**\n * Validate token (basic check)\n */\n async validateToken(token: string, resourceType: string): Promise<boolean> {\n if (!this.config.validateToken) {\n return true;\n }\n \n try {\n validateAccessToken(token);\n return true;\n } catch {\n return false;\n }\n }\n \n /**\n * Initialize resolver\n */\n async initialize(): Promise<void> {\n this.logger.info('APITokenResolver initialized', {\n tenantManagerUrl: this.config.tenantManagerUrl,\n endpointPath: this.config.endpointPath,\n cacheEnabled: this.config.cacheTokens,\n timeoutMs: this.config.timeoutMs\n });\n \n // Validate configuration\n try {\n new URL(this.config.tenantManagerUrl);\n } catch {\n throw new Error(`Invalid tenantManagerUrl: ${this.config.tenantManagerUrl}`);\n }\n }\n \n /**\n * Cleanup resources\n */\n async cleanup(): Promise<void> {\n this.tokenCache.clear();\n this.logger.info('APITokenResolver cleaned up');\n }\n \n /**\n * Clear token cache\n */\n clearCache(): void {\n this.tokenCache.clear();\n this.logger.debug('Token cache cleared');\n }\n \n /**\n * Get cache statistics\n */\n getCacheStats(): { size: number; keys: string[] } {\n return {\n size: this.tokenCache.size,\n keys: Array.from(this.tokenCache.keys())\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,4BAA4B;AACrC,SAAS,oBAAiC;AAC1C,SAAS,2BAA2B;AAsE7B,MAAM,iBAAkD;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAgC;AAC1C,QAAI,CAAC,OAAO,kBAAkB;AAC5B,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AACA,QAAI,CAAC,OAAO,cAAc;AACxB,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AAEA,SAAK,SAAS;AAAA,MACZ,kBAAkB,OAAO,iBAAiB,QAAQ,OAAO,EAAE;AAAA;AAAA,MAC3D,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO,gBAAgB;AAAA,MACrC,WAAW,OAAO,aAAa;AAAA,MAC/B,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,eAAe,OAAO,iBAAiB;AAAA,MACvC,eAAe,OAAO,iBAAiB,CAAC;AAAA,MACxC,aAAa,OAAO,eAAe;AAAA,MACnC,UAAU,OAAO,YAAY;AAAA;AAAA,MAC7B,aAAa,OAAO,eAAe;AAAA,IACrC;AAEA,SAAK,SAAS,aAAa,EAAE,SAAS,MAAM,OAAO,OAAO,CAAC;AAC3D,SAAK,aAAa,oBAAI,IAAI;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAgB,cAA8C;AAE/E,QAAI,KAAK,OAAO,aAAa;AAC3B,YAAM,WAAW,GAAG,MAAM,IAAI,YAAY;AAC1C,YAAM,SAAS,KAAK,WAAW,IAAI,QAAQ;AAE3C,UAAI,UAAU,OAAO,YAAY,KAAK,IAAI,GAAG;AAC3C,aAAK,OAAO,MAAM,8BAA8B;AAAA,UAC9C;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AACD,eAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,MAAM,KAAK,SAAS,QAAQ,YAAY;AAE9C,QAAI;AACF,WAAK,OAAO,MAAM,8BAA8B;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAGD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,SAAS;AAE5E,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,iBAAiB,UAAU,KAAK,OAAO,YAAY;AAAA,UACnD,gBAAgB;AAAA,UAChB,GAAG,KAAK,OAAO;AAAA,QACjB;AAAA,QACA,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAEtB,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,eAAe;AAEnE,aAAK,OAAO,KAAK,sBAAsB;AAAA,UACrC;AAAA,UACA;AAAA,UACA,QAAQ,SAAS;AAAA,UACjB,OAAO;AAAA,QACT,CAAC;AAED,YAAI,KAAK,OAAO,gBAAgB;AAC9B,gBAAM,IAAI,qBAAqB,QAAQ,cAAc;AAAA,YACnD,QAAQ,SAAS;AAAA,YACjB,OAAO;AAAA,YACP;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT;AAGA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAM,QAAQ,KAAK,eAAe,KAAK,gBAAgB,KAAK;AAE5D,UAAI,CAAC,OAAO;AACV,aAAK,OAAO,KAAK,8BAA8B;AAAA,UAC7C;AAAA,UACA;AAAA,UACA,cAAc,OAAO,KAAK,QAAQ,CAAC,CAAC;AAAA,QACtC,CAAC;AAED,YAAI,KAAK,OAAO,gBAAgB;AAC9B,gBAAM,IAAI,qBAAqB,QAAQ,cAAc;AAAA,YACnD,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT;AAGA,UAAI,KAAK,OAAO,eAAe;AAC7B,YAAI;AACF,8BAAoB,KAAK;AAAA,QAC3B,SAAS,OAAO;AACd,eAAK,OAAO,MAAM,2BAA2B,OAAgB;AAAA,YAC3D;AAAA,YACA;AAAA,UACF,CAAC;AAED,cAAI,KAAK,OAAO,gBAAgB;AAC9B,kBAAM,IAAI,qBAAqB,QAAQ,cAAc;AAAA,cACnD,QAAQ;AAAA,YACV,CAAC;AAAA,UACH;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAGA,UAAI,KAAK,OAAO,aAAa;AAC3B,cAAM,WAAW,GAAG,MAAM,IAAI,YAAY;AAC1C,aAAK,WAAW,IAAI,UAAU;AAAA,UAC5B;AAAA,UACA,WAAW,KAAK,IAAI,IAAI,KAAK,OAAO;AAAA,QACtC,CAAC;AAED,aAAK,OAAO,MAAM,gBAAgB;AAAA,UAChC;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,KAAK,OAAO;AAAA,QACnB,CAAC;AAAA,MACH;AAEA,WAAK,OAAO,KAAK,+BAA+B;AAAA,QAC9C;AAAA,QACA;AAAA,QACA,aAAa,MAAM;AAAA,MACrB,CAAC;AAED,aAAO;AAAA,IAET,SAAS,OAAO;AACd,UAAI,iBAAiB,sBAAsB;AACzC,cAAM;AAAA,MACR;AAEA,WAAK,OAAO,MAAM,qBAAqB,OAAgB;AAAA,QACrD;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,UAAI,KAAK,OAAO,gBAAgB;AAC9B,cAAM,IAAI,qBAAqB,QAAQ,cAAc;AAAA,UACnD,QAAQ,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UACjD;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,QAAgB,cAA8B;AAC7D,UAAM,OAAO,KAAK,OAAO,aACtB,QAAQ,WAAW,mBAAmB,MAAM,CAAC,EAC7C,QAAQ,iBAAiB,mBAAmB,YAAY,CAAC;AAE5D,WAAO,GAAG,KAAK,OAAO,gBAAgB,GAAG,IAAI;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAgB,cAA8C;AAC/E,SAAK,OAAO,KAAK,4BAA4B,EAAE,QAAQ,aAAa,CAAC;AAGrE,UAAM,WAAW,GAAG,MAAM,IAAI,YAAY;AAC1C,SAAK,WAAW,OAAO,QAAQ;AAE/B,WAAO,KAAK,aAAa,QAAQ,YAAY;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,OAAe,cAAwC;AACzE,QAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,aAAO;AAAA,IACT;AAEA,QAAI;AACF,0BAAoB,KAAK;AACzB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,SAAK,OAAO,KAAK,gCAAgC;AAAA,MAC/C,kBAAkB,KAAK,OAAO;AAAA,MAC9B,cAAc,KAAK,OAAO;AAAA,MAC1B,cAAc,KAAK,OAAO;AAAA,MAC1B,WAAW,KAAK,OAAO;AAAA,IACzB,CAAC;AAGD,QAAI;AACF,UAAI,IAAI,KAAK,OAAO,gBAAgB;AAAA,IACtC,QAAQ;AACN,YAAM,IAAI,MAAM,6BAA6B,KAAK,OAAO,gBAAgB,EAAE;AAAA,IAC7E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,SAAK,WAAW,MAAM;AACtB,SAAK,OAAO,KAAK,6BAA6B;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,WAAW,MAAM;AACtB,SAAK,OAAO,MAAM,qBAAqB;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAkD;AAChD,WAAO;AAAA,MACL,MAAM,KAAK,WAAW;AAAA,MACtB,MAAM,MAAM,KAAK,KAAK,WAAW,KAAK,CAAC;AAAA,IACzC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -4,8 +4,20 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
SimpleTokenResolver
|
|
6
6
|
} from "./simple-resolver.js";
|
|
7
|
+
import {
|
|
8
|
+
JWTAuthProvider
|
|
9
|
+
} from "./jwt-provider.js";
|
|
10
|
+
import {
|
|
11
|
+
JWTTokenResolver
|
|
12
|
+
} from "./jwt-token-resolver.js";
|
|
13
|
+
import {
|
|
14
|
+
APITokenResolver
|
|
15
|
+
} from "./api-token-resolver.js";
|
|
7
16
|
export {
|
|
17
|
+
APITokenResolver,
|
|
8
18
|
EnvAuthProvider,
|
|
19
|
+
JWTAuthProvider,
|
|
20
|
+
JWTTokenResolver,
|
|
9
21
|
SimpleTokenResolver
|
|
10
22
|
};
|
|
11
23
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/auth/providers/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Authentication providers module exports\n */\n\n// Environment-based provider\nexport {
|
|
5
|
-
"mappings": "AAKA;AAAA,EACE;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,OAEK;",
|
|
4
|
+
"sourcesContent": ["/**\n * Authentication providers module exports\n */\n\n// Environment-based provider\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig\n} from './env-provider.js';\n\n// Simple token resolver\nexport {\n SimpleTokenResolver,\n type SimpleTokenResolverConfig\n} from './simple-resolver.js';\n\n// JWT provider and resolver\nexport {\n JWTAuthProvider,\n type JWTAuthProviderConfig,\n type JWTPayload\n} from './jwt-provider.js';\n\nexport {\n JWTTokenResolver,\n type JWTTokenResolverConfig\n} from './jwt-token-resolver.js';\n\n// API-based token resolver\nexport {\n APITokenResolver,\n type APITokenResolverConfig\n} from './api-token-resolver.js';\n"],
|
|
5
|
+
"mappings": "AAKA;AAAA,EACE;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,OAGK;AAEP;AAAA,EACE;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,OAEK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { BaseAuthProvider } from "../base-provider.js";
|
|
2
|
+
class JWTAuthProvider extends BaseAuthProvider {
|
|
3
|
+
jwtConfig;
|
|
4
|
+
/**
|
|
5
|
+
* Token cache for extracted tokens
|
|
6
|
+
* Maps userId → resourceType → token
|
|
7
|
+
*/
|
|
8
|
+
tokenCache = /* @__PURE__ */ new Map();
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super(config);
|
|
11
|
+
if (!config.jwtSecret) {
|
|
12
|
+
throw new Error("jwtSecret is required for JWTAuthProvider");
|
|
13
|
+
}
|
|
14
|
+
this.jwtConfig = {
|
|
15
|
+
...config,
|
|
16
|
+
jwtSecret: config.jwtSecret,
|
|
17
|
+
algorithm: config.algorithm ?? "HS256",
|
|
18
|
+
extractTokens: config.extractTokens ?? true,
|
|
19
|
+
userIdClaim: config.userIdClaim ?? "sub",
|
|
20
|
+
tokensClaim: config.tokensClaim ?? "tokens",
|
|
21
|
+
validateExpiration: config.validateExpiration ?? true,
|
|
22
|
+
clockTolerance: config.clockTolerance ?? 0,
|
|
23
|
+
errorMessages: config.errorMessages ?? {},
|
|
24
|
+
cacheResults: config.cacheResults ?? false,
|
|
25
|
+
cacheTtl: config.cacheTtl ?? 6e4
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Authenticate request by validating JWT
|
|
30
|
+
*/
|
|
31
|
+
async doAuthenticate(context) {
|
|
32
|
+
const token = this.extractBearerToken(context);
|
|
33
|
+
if (!token) {
|
|
34
|
+
return this.createFailureResult(
|
|
35
|
+
this.jwtConfig.errorMessages?.noAuth || "No JWT token provided"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const jwt = await import("jsonwebtoken");
|
|
40
|
+
const decoded = jwt.verify(token, this.jwtConfig.jwtSecret, {
|
|
41
|
+
algorithms: [this.jwtConfig.algorithm],
|
|
42
|
+
clockTolerance: this.jwtConfig.clockTolerance
|
|
43
|
+
});
|
|
44
|
+
const userId = decoded[this.jwtConfig.userIdClaim] || decoded.userId || decoded.sub;
|
|
45
|
+
if (!userId) {
|
|
46
|
+
return this.createFailureResult("JWT does not contain user ID");
|
|
47
|
+
}
|
|
48
|
+
this.logger.debug("JWT validated successfully", {
|
|
49
|
+
userId,
|
|
50
|
+
hasTokens: !!decoded[this.jwtConfig.tokensClaim]
|
|
51
|
+
});
|
|
52
|
+
if (this.jwtConfig.extractTokens && decoded[this.jwtConfig.tokensClaim]) {
|
|
53
|
+
const tokens = decoded[this.jwtConfig.tokensClaim];
|
|
54
|
+
const userTokens = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const [resourceType, resourceToken] of Object.entries(tokens)) {
|
|
56
|
+
userTokens.set(resourceType, resourceToken);
|
|
57
|
+
}
|
|
58
|
+
this.tokenCache.set(userId, userTokens);
|
|
59
|
+
this.logger.debug("Extracted tokens from JWT", {
|
|
60
|
+
userId,
|
|
61
|
+
resourceTypes: Object.keys(tokens)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return this.createSuccessResult(userId, {
|
|
65
|
+
exp: decoded.exp,
|
|
66
|
+
iat: decoded.iat,
|
|
67
|
+
hasEmbeddedTokens: !!decoded[this.jwtConfig.tokensClaim]
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.logger.warn("JWT validation failed", {
|
|
71
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
72
|
+
});
|
|
73
|
+
if (error instanceof Error) {
|
|
74
|
+
if (error.message.includes("expired")) {
|
|
75
|
+
return this.createFailureResult(
|
|
76
|
+
this.jwtConfig.errorMessages?.expiredAuth || "JWT token has expired"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (error.message.includes("invalid")) {
|
|
80
|
+
return this.createFailureResult(
|
|
81
|
+
this.jwtConfig.errorMessages?.invalidAuth || "Invalid JWT token"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return this.createFailureResult("JWT validation failed");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate provider configuration
|
|
90
|
+
*/
|
|
91
|
+
async validate() {
|
|
92
|
+
if (!this.jwtConfig.jwtSecret || this.jwtConfig.jwtSecret.length < 32) {
|
|
93
|
+
this.logger.error("JWT secret must be at least 32 characters");
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await import("jsonwebtoken");
|
|
98
|
+
} catch {
|
|
99
|
+
this.logger.error("jsonwebtoken package is required. Install it with: npm install jsonwebtoken");
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get cached token for a user and resource
|
|
106
|
+
*/
|
|
107
|
+
getCachedToken(userId, resourceType) {
|
|
108
|
+
return this.tokenCache.get(userId)?.get(resourceType) || null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Clear token cache
|
|
112
|
+
*/
|
|
113
|
+
clearTokenCache() {
|
|
114
|
+
this.tokenCache.clear();
|
|
115
|
+
this.logger.debug("Token cache cleared");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get token cache statistics
|
|
119
|
+
*/
|
|
120
|
+
getTokenCacheStats() {
|
|
121
|
+
const users = Array.from(this.tokenCache.entries()).map(([userId, tokens]) => ({
|
|
122
|
+
userId,
|
|
123
|
+
resourceTypes: Array.from(tokens.keys())
|
|
124
|
+
}));
|
|
125
|
+
const totalTokens = users.reduce((sum, user) => sum + user.resourceTypes.length, 0);
|
|
126
|
+
return {
|
|
127
|
+
userCount: this.tokenCache.size,
|
|
128
|
+
totalTokens,
|
|
129
|
+
users
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
JWTAuthProvider
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=jwt-provider.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/auth/providers/jwt-provider.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * JWT authentication provider\n * \n * Reference implementation for JWT-based authentication.\n * Validates JWT tokens and optionally extracts embedded resource tokens.\n */\n\nimport { BaseAuthProvider } from '../base-provider.js';\nimport type { AuthProviderConfig } from '../types.js';\nimport type { RequestContext, AuthResult } from '../../types.js';\nimport { AuthenticationError, MissingCredentialsError, InvalidTokenError } from '../../utils/errors.js';\n\n/**\n * JWT payload structure\n */\nexport interface JWTPayload {\n /**\n * User ID (standard JWT claim: 'sub' or custom 'userId')\n */\n sub?: string;\n userId?: string;\n \n /**\n * Optional: Embedded resource tokens\n * { instagram: \"token1\", github: \"token2\" }\n */\n tokens?: Record<string, string>;\n \n /**\n * Expiration time (standard JWT claim)\n */\n exp?: number;\n \n /**\n * Issued at (standard JWT claim)\n */\n iat?: number;\n \n /**\n * Additional custom claims\n */\n [key: string]: any;\n}\n\n/**\n * Configuration for JWTAuthProvider\n */\nexport interface JWTAuthProviderConfig extends AuthProviderConfig {\n /**\n * JWT secret for verification\n */\n jwtSecret: string;\n \n /**\n * JWT algorithm\n * @default 'HS256'\n */\n algorithm?: string;\n \n /**\n * Whether to extract embedded tokens from JWT\n * If true, tokens will be cached for JWTTokenResolver\n * @default true\n */\n extractTokens?: boolean;\n \n /**\n * Custom user ID claim name\n * @default 'sub' (falls back to 'userId')\n */\n userIdClaim?: string;\n \n /**\n * Custom tokens claim name\n * @default 'tokens'\n */\n tokensClaim?: string;\n \n /**\n * Whether to validate token expiration\n * @default true\n */\n validateExpiration?: boolean;\n \n /**\n * Clock tolerance in seconds for exp/nbf claims\n * @default 0\n */\n clockTolerance?: number;\n}\n\n/**\n * JWT authentication provider\n * \n * Validates JWT tokens issued by the tenant manager.\n * Optionally extracts embedded resource tokens from the JWT payload.\n * \n * @example\n * ```typescript\n * // Basic usage\n * const provider = new JWTAuthProvider({\n * jwtSecret: process.env.JWT_SECRET\n * });\n * \n * // With token extraction\n * const provider = new JWTAuthProvider({\n * jwtSecret: process.env.JWT_SECRET,\n * extractTokens: true\n * });\n * ```\n */\nexport class JWTAuthProvider extends BaseAuthProvider {\n private jwtConfig: Required<JWTAuthProviderConfig>;\n \n /**\n * Token cache for extracted tokens\n * Maps userId \u2192 resourceType \u2192 token\n */\n public readonly tokenCache = new Map<string, Map<string, string>>();\n \n constructor(config: JWTAuthProviderConfig) {\n super(config);\n \n if (!config.jwtSecret) {\n throw new Error('jwtSecret is required for JWTAuthProvider');\n }\n \n this.jwtConfig = {\n ...config,\n jwtSecret: config.jwtSecret,\n algorithm: config.algorithm ?? 'HS256',\n extractTokens: config.extractTokens ?? true,\n userIdClaim: config.userIdClaim ?? 'sub',\n tokensClaim: config.tokensClaim ?? 'tokens',\n validateExpiration: config.validateExpiration ?? true,\n clockTolerance: config.clockTolerance ?? 0,\n errorMessages: config.errorMessages ?? {},\n cacheResults: config.cacheResults ?? false,\n cacheTtl: config.cacheTtl ?? 60000\n };\n }\n \n /**\n * Authenticate request by validating JWT\n */\n protected async doAuthenticate(context: RequestContext): Promise<AuthResult> {\n // Extract bearer token\n const token = this.extractBearerToken(context);\n \n if (!token) {\n return this.createFailureResult(\n this.jwtConfig.errorMessages?.noAuth || 'No JWT token provided'\n );\n }\n \n try {\n // Dynamically import jsonwebtoken (optional dependency)\n // @ts-ignore - Dynamic import of optional dependency\n const jwt = await import('jsonwebtoken');\n \n // Verify JWT\n const decoded = jwt.verify(token, this.jwtConfig.jwtSecret, {\n algorithms: [this.jwtConfig.algorithm as any],\n clockTolerance: this.jwtConfig.clockTolerance\n }) as JWTPayload;\n \n // Extract user ID\n const userId = decoded[this.jwtConfig.userIdClaim] || decoded.userId || decoded.sub;\n \n if (!userId) {\n return this.createFailureResult('JWT does not contain user ID');\n }\n \n this.logger.debug('JWT validated successfully', {\n userId,\n hasTokens: !!decoded[this.jwtConfig.tokensClaim]\n });\n \n // Extract embedded tokens if enabled\n if (this.jwtConfig.extractTokens && decoded[this.jwtConfig.tokensClaim]) {\n const tokens = decoded[this.jwtConfig.tokensClaim] as Record<string, string>;\n \n // Cache tokens for resolver\n const userTokens = new Map<string, string>();\n for (const [resourceType, resourceToken] of Object.entries(tokens)) {\n userTokens.set(resourceType, resourceToken);\n }\n this.tokenCache.set(userId, userTokens);\n \n this.logger.debug('Extracted tokens from JWT', {\n userId,\n resourceTypes: Object.keys(tokens)\n });\n }\n \n return this.createSuccessResult(userId, {\n exp: decoded.exp,\n iat: decoded.iat,\n hasEmbeddedTokens: !!decoded[this.jwtConfig.tokensClaim]\n });\n \n } catch (error) {\n this.logger.warn('JWT validation failed', {\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n \n if (error instanceof Error) {\n if (error.message.includes('expired')) {\n return this.createFailureResult(\n this.jwtConfig.errorMessages?.expiredAuth || 'JWT token has expired'\n );\n }\n if (error.message.includes('invalid')) {\n return this.createFailureResult(\n this.jwtConfig.errorMessages?.invalidAuth || 'Invalid JWT token'\n );\n }\n }\n \n return this.createFailureResult('JWT validation failed');\n }\n }\n \n /**\n * Validate provider configuration\n */\n async validate(): Promise<boolean> {\n if (!this.jwtConfig.jwtSecret || this.jwtConfig.jwtSecret.length < 32) {\n this.logger.error('JWT secret must be at least 32 characters');\n return false;\n }\n \n // Try to import jsonwebtoken\n try {\n // @ts-ignore - Dynamic import of optional dependency\n await import('jsonwebtoken');\n } catch {\n this.logger.error('jsonwebtoken package is required. Install it with: npm install jsonwebtoken');\n return false;\n }\n \n return true;\n }\n \n /**\n * Get cached token for a user and resource\n */\n getCachedToken(userId: string, resourceType: string): string | null {\n return this.tokenCache.get(userId)?.get(resourceType) || null;\n }\n \n /**\n * Clear token cache\n */\n clearTokenCache(): void {\n this.tokenCache.clear();\n this.logger.debug('Token cache cleared');\n }\n \n /**\n * Get token cache statistics\n */\n getTokenCacheStats(): {\n userCount: number;\n totalTokens: number;\n users: Array<{ userId: string; resourceTypes: string[] }>;\n } {\n const users = Array.from(this.tokenCache.entries()).map(([userId, tokens]) => ({\n userId,\n resourceTypes: Array.from(tokens.keys())\n }));\n \n const totalTokens = users.reduce((sum, user) => sum + user.resourceTypes.length, 0);\n \n return {\n userCount: this.tokenCache.size,\n totalTokens,\n users\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { TokenResolutionError } from "../../utils/errors.js";
|
|
2
|
+
import { createLogger } from "../../utils/logger.js";
|
|
3
|
+
class JWTTokenResolver {
|
|
4
|
+
config;
|
|
5
|
+
logger;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
if (!config.authProvider) {
|
|
8
|
+
throw new Error("authProvider is required for JWTTokenResolver");
|
|
9
|
+
}
|
|
10
|
+
this.config = {
|
|
11
|
+
authProvider: config.authProvider,
|
|
12
|
+
throwOnMissing: config.throwOnMissing ?? true,
|
|
13
|
+
cacheTokens: config.cacheTokens ?? true,
|
|
14
|
+
cacheTtl: config.cacheTtl ?? 3e5,
|
|
15
|
+
autoRefresh: config.autoRefresh ?? false
|
|
16
|
+
};
|
|
17
|
+
this.logger = createLogger({ enabled: true, level: "info" });
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve token from JWT auth provider's cache
|
|
21
|
+
*/
|
|
22
|
+
async resolveToken(userId, resourceType) {
|
|
23
|
+
const token = this.config.authProvider.getCachedToken(userId, resourceType);
|
|
24
|
+
if (!token) {
|
|
25
|
+
const errorMessage = `No ${resourceType} token found in JWT for user ${userId}`;
|
|
26
|
+
this.logger.warn("Token resolution failed", {
|
|
27
|
+
userId,
|
|
28
|
+
resourceType,
|
|
29
|
+
reason: "Token not found in JWT cache"
|
|
30
|
+
});
|
|
31
|
+
if (this.config.throwOnMissing) {
|
|
32
|
+
throw new TokenResolutionError(userId, resourceType, {
|
|
33
|
+
reason: "Token not embedded in JWT",
|
|
34
|
+
hint: "Ensure tenant manager includes tokens in JWT payload"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
this.logger.debug("Token resolved from JWT cache", {
|
|
40
|
+
userId,
|
|
41
|
+
resourceType,
|
|
42
|
+
tokenLength: token.length
|
|
43
|
+
});
|
|
44
|
+
return token;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Refresh token (not supported for JWT-embedded tokens)
|
|
48
|
+
*/
|
|
49
|
+
async refreshToken(userId, resourceType) {
|
|
50
|
+
this.logger.warn("Token refresh not supported for JWT-embedded tokens", {
|
|
51
|
+
userId,
|
|
52
|
+
resourceType,
|
|
53
|
+
hint: "User must obtain new JWT from tenant manager"
|
|
54
|
+
});
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate token (checks if token exists in cache)
|
|
59
|
+
*/
|
|
60
|
+
async validateToken(token, resourceType) {
|
|
61
|
+
return !!(token && token.length > 0);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Initialize resolver
|
|
65
|
+
*/
|
|
66
|
+
async initialize() {
|
|
67
|
+
this.logger.info("JWTTokenResolver initialized", {
|
|
68
|
+
throwOnMissing: this.config.throwOnMissing
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Cleanup resources
|
|
73
|
+
*/
|
|
74
|
+
async cleanup() {
|
|
75
|
+
this.logger.info("JWTTokenResolver cleaned up");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get available resource types for a user
|
|
79
|
+
*/
|
|
80
|
+
getAvailableResources(userId) {
|
|
81
|
+
const userTokens = this.config.authProvider.tokenCache.get(userId);
|
|
82
|
+
return userTokens ? Array.from(userTokens.keys()) : [];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if token is available for a user and resource
|
|
86
|
+
*/
|
|
87
|
+
hasToken(userId, resourceType) {
|
|
88
|
+
return !!this.config.authProvider.getCachedToken(userId, resourceType);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
JWTTokenResolver
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=jwt-token-resolver.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/auth/providers/jwt-token-resolver.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * JWT token resolver\n * \n * Resolves tokens that were extracted from JWT by JWTAuthProvider.\n * Works in conjunction with JWTAuthProvider for JWT-embedded token approach.\n */\n\nimport type { ResourceTokenResolver, TokenResolverConfig } from '../types.js';\nimport type { JWTAuthProvider } from './jwt-provider.js';\nimport { TokenResolutionError } from '../../utils/errors.js';\nimport { createLogger, type Logger } from '../../utils/logger.js';\n\n/**\n * Configuration for JWTTokenResolver\n */\nexport interface JWTTokenResolverConfig extends TokenResolverConfig {\n /**\n * JWTAuthProvider instance that extracts tokens\n */\n authProvider: JWTAuthProvider;\n \n /**\n * Whether to throw error if token is not found\n * @default true\n */\n throwOnMissing?: boolean;\n}\n\n/**\n * JWT token resolver\n * \n * Resolves tokens that were cached by JWTAuthProvider during authentication.\n * This is used for the JWT-embedded token approach where the JWT contains\n * all resource tokens.\n * \n * @example\n * ```typescript\n * const authProvider = new JWTAuthProvider({\n * jwtSecret: process.env.JWT_SECRET,\n * extractTokens: true\n * });\n * \n * const tokenResolver = new JWTTokenResolver({\n * authProvider\n * });\n * \n * // JWT structure:\n * // {\n * // \"userId\": \"user-123\",\n * // \"tokens\": {\n * // \"instagram\": \"IGQVJXabc...\",\n * // \"github\": \"ghp_abc123...\"\n * // }\n * // }\n * ```\n */\nexport class JWTTokenResolver implements ResourceTokenResolver {\n private config: Required<JWTTokenResolverConfig>;\n private logger: Logger;\n \n constructor(config: JWTTokenResolverConfig) {\n if (!config.authProvider) {\n throw new Error('authProvider is required for JWTTokenResolver');\n }\n \n this.config = {\n authProvider: config.authProvider,\n throwOnMissing: config.throwOnMissing ?? true,\n cacheTokens: config.cacheTokens ?? true,\n cacheTtl: config.cacheTtl ?? 300000,\n autoRefresh: config.autoRefresh ?? false\n };\n \n this.logger = createLogger({ enabled: true, level: 'info' });\n }\n \n /**\n * Resolve token from JWT auth provider's cache\n */\n async resolveToken(userId: string, resourceType: string): Promise<string | null> {\n // Get token from auth provider's cache\n const token = this.config.authProvider.getCachedToken(userId, resourceType);\n \n if (!token) {\n const errorMessage = `No ${resourceType} token found in JWT for user ${userId}`;\n \n this.logger.warn('Token resolution failed', {\n userId,\n resourceType,\n reason: 'Token not found in JWT cache'\n });\n \n if (this.config.throwOnMissing) {\n throw new TokenResolutionError(userId, resourceType, {\n reason: 'Token not embedded in JWT',\n hint: 'Ensure tenant manager includes tokens in JWT payload'\n });\n }\n \n return null;\n }\n \n this.logger.debug('Token resolved from JWT cache', {\n userId,\n resourceType,\n tokenLength: token.length\n });\n \n return token;\n }\n \n /**\n * Refresh token (not supported for JWT-embedded tokens)\n */\n async refreshToken(userId: string, resourceType: string): Promise<string | null> {\n this.logger.warn('Token refresh not supported for JWT-embedded tokens', {\n userId,\n resourceType,\n hint: 'User must obtain new JWT from tenant manager'\n });\n \n return null;\n }\n \n /**\n * Validate token (checks if token exists in cache)\n */\n async validateToken(token: string, resourceType: string): Promise<boolean> {\n // Basic validation - just check if token is non-empty\n return !!(token && token.length > 0);\n }\n \n /**\n * Initialize resolver\n */\n async initialize(): Promise<void> {\n this.logger.info('JWTTokenResolver initialized', {\n throwOnMissing: this.config.throwOnMissing\n });\n }\n \n /**\n * Cleanup resources\n */\n async cleanup(): Promise<void> {\n // Token cache is managed by auth provider\n this.logger.info('JWTTokenResolver cleaned up');\n }\n \n /**\n * Get available resource types for a user\n */\n getAvailableResources(userId: string): string[] {\n const userTokens = this.config.authProvider.tokenCache.get(userId);\n return userTokens ? Array.from(userTokens.keys()) : [];\n }\n \n /**\n * Check if token is available for a user and resource\n */\n hasToken(userId: string, resourceType: string): boolean {\n return !!this.config.authProvider.getCachedToken(userId, resourceType);\n }\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,4BAA4B;AACrC,SAAS,oBAAiC;AA8CnC,MAAM,iBAAkD;AAAA,EACrD;AAAA,EACA;AAAA,EAER,YAAY,QAAgC;AAC1C,QAAI,CAAC,OAAO,cAAc;AACxB,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AAEA,SAAK,SAAS;AAAA,MACZ,cAAc,OAAO;AAAA,MACrB,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,aAAa,OAAO,eAAe;AAAA,MACnC,UAAU,OAAO,YAAY;AAAA,MAC7B,aAAa,OAAO,eAAe;AAAA,IACrC;AAEA,SAAK,SAAS,aAAa,EAAE,SAAS,MAAM,OAAO,OAAO,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAgB,cAA8C;AAE/E,UAAM,QAAQ,KAAK,OAAO,aAAa,eAAe,QAAQ,YAAY;AAE1E,QAAI,CAAC,OAAO;AACV,YAAM,eAAe,MAAM,YAAY,gCAAgC,MAAM;AAE7E,WAAK,OAAO,KAAK,2BAA2B;AAAA,QAC1C;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAED,UAAI,KAAK,OAAO,gBAAgB;AAC9B,cAAM,IAAI,qBAAqB,QAAQ,cAAc;AAAA,UACnD,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT;AAEA,SAAK,OAAO,MAAM,iCAAiC;AAAA,MACjD;AAAA,MACA;AAAA,MACA,aAAa,MAAM;AAAA,IACrB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAgB,cAA8C;AAC/E,SAAK,OAAO,KAAK,uDAAuD;AAAA,MACtE;AAAA,MACA;AAAA,MACA,MAAM;AAAA,IACR,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,OAAe,cAAwC;AAEzE,WAAO,CAAC,EAAE,SAAS,MAAM,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,SAAK,OAAO,KAAK,gCAAgC;AAAA,MAC/C,gBAAgB,KAAK,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAE7B,SAAK,OAAO,KAAK,6BAA6B;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsB,QAA0B;AAC9C,UAAM,aAAa,KAAK,OAAO,aAAa,WAAW,IAAI,MAAM;AACjE,WAAO,aAAa,MAAM,KAAK,WAAW,KAAK,CAAC,IAAI,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,QAAgB,cAA+B;AACtD,WAAO,CAAC,CAAC,KAAK,OAAO,aAAa,eAAe,QAAQ,YAAY;AAAA,EACvE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
import { BaseAuthProvider } from "./auth/base-provider.js";
|
|
17
17
|
import {
|
|
18
18
|
EnvAuthProvider,
|
|
19
|
-
SimpleTokenResolver
|
|
19
|
+
SimpleTokenResolver,
|
|
20
|
+
JWTAuthProvider,
|
|
21
|
+
JWTTokenResolver,
|
|
22
|
+
APITokenResolver
|
|
20
23
|
} from "./auth/providers/index.js";
|
|
21
24
|
import {
|
|
22
25
|
MCPAuthError,
|
|
@@ -57,6 +60,7 @@ import {
|
|
|
57
60
|
validateAccessToken
|
|
58
61
|
} from "./utils/index.js";
|
|
59
62
|
export {
|
|
63
|
+
APITokenResolver,
|
|
60
64
|
AuthenticatedMCPServer,
|
|
61
65
|
AuthenticatedServerWrapper,
|
|
62
66
|
AuthenticatedTool,
|
|
@@ -65,6 +69,8 @@ export {
|
|
|
65
69
|
ConfigurationError,
|
|
66
70
|
EnvAuthProvider,
|
|
67
71
|
InvalidTokenError,
|
|
72
|
+
JWTAuthProvider,
|
|
73
|
+
JWTTokenResolver,
|
|
68
74
|
LogLevel,
|
|
69
75
|
Logger,
|
|
70
76
|
MCPAuthError,
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * @prmichaelsen/mcp-auth\n *\n * Authentication and multi-tenancy framework for MCP (Model Context Protocol) servers.\n *\n * Supports two complementary patterns:\n * 1. **Server Wrapping** - Wrap existing MCP servers without modification (MCP-level auth)\n * 2. **Tool-Level Auth** - Build new MCP servers with integrated auth\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// PATTERN 1: SERVER WRAPPING (MCP-Level Auth)\n// ============================================================================\n// Use this to wrap existing MCP servers without modifying them\n// Ideal for multi-tenant services that host multiple MCP servers\n\nexport {\n wrapServer,\n AuthenticatedServerWrapper,\n type ServerWrapperConfig,\n type MCPServerFactory,\n type NormalizedServerWrapperConfig\n} from './wrapper/index.js';\n\n// ============================================================================\n// PATTERN 2: TOOL-LEVEL AUTH\n// ============================================================================\n// Use this to build new MCP servers with integrated authentication\n// Provides fine-grained control over auth per tool\n\nexport {\n AuthenticatedMCPServer,\n type ServerConfig,\n type NormalizedServerConfig,\n withAuth,\n compose,\n withLogging,\n withRateLimit,\n withTimeout,\n withRetry,\n type Tool,\n AuthenticatedTool,\n createAuthenticatedTool,\n type AuthenticatedToolHandler\n} from './server/index.js';\n\n// ============================================================================\n// SHARED: CORE TYPES\n// ============================================================================\n\nexport type {\n TransportType,\n RequestContext,\n AuthResult,\n TransportConfig,\n RateLimitConfig,\n LoggingConfig,\n MiddlewareConfig,\n PoolingConfig,\n Result,\n AsyncFunction,\n ToolHandler,\n Middleware\n} from './types.js';\n\n// ============================================================================\n// SHARED: AUTHENTICATION\n// ============================================================================\n\nexport type {\n AuthProvider,\n ResourceTokenResolver,\n AuthenticatedContext,\n AuthProviderConfig,\n TokenResolverConfig\n} from './auth/types.js';\n\nexport { BaseAuthProvider } from './auth/base-provider.js';\n\n// Providers\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig,\n SimpleTokenResolver,\n type SimpleTokenResolverConfig\n} from './auth/providers/index.js';\n\n//
|
|
5
|
-
"mappings": "AAkBA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAQP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OAEK;AAiCP,SAAS,wBAAwB;AAGjC;AAAA,EACE;AAAA,EAEA;AAAA,OAEK;
|
|
4
|
+
"sourcesContent": ["/**\n * @prmichaelsen/mcp-auth\n *\n * Authentication and multi-tenancy framework for MCP (Model Context Protocol) servers.\n *\n * Supports two complementary patterns:\n * 1. **Server Wrapping** - Wrap existing MCP servers without modification (MCP-level auth)\n * 2. **Tool-Level Auth** - Build new MCP servers with integrated auth\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// PATTERN 1: SERVER WRAPPING (MCP-Level Auth)\n// ============================================================================\n// Use this to wrap existing MCP servers without modifying them\n// Ideal for multi-tenant services that host multiple MCP servers\n\nexport {\n wrapServer,\n AuthenticatedServerWrapper,\n type ServerWrapperConfig,\n type MCPServerFactory,\n type NormalizedServerWrapperConfig\n} from './wrapper/index.js';\n\n// ============================================================================\n// PATTERN 2: TOOL-LEVEL AUTH\n// ============================================================================\n// Use this to build new MCP servers with integrated authentication\n// Provides fine-grained control over auth per tool\n\nexport {\n AuthenticatedMCPServer,\n type ServerConfig,\n type NormalizedServerConfig,\n withAuth,\n compose,\n withLogging,\n withRateLimit,\n withTimeout,\n withRetry,\n type Tool,\n AuthenticatedTool,\n createAuthenticatedTool,\n type AuthenticatedToolHandler\n} from './server/index.js';\n\n// ============================================================================\n// SHARED: CORE TYPES\n// ============================================================================\n\nexport type {\n TransportType,\n RequestContext,\n AuthResult,\n TransportConfig,\n RateLimitConfig,\n LoggingConfig,\n MiddlewareConfig,\n PoolingConfig,\n Result,\n AsyncFunction,\n ToolHandler,\n Middleware\n} from './types.js';\n\n// ============================================================================\n// SHARED: AUTHENTICATION\n// ============================================================================\n\nexport type {\n AuthProvider,\n ResourceTokenResolver,\n AuthenticatedContext,\n AuthProviderConfig,\n TokenResolverConfig\n} from './auth/types.js';\n\nexport { BaseAuthProvider } from './auth/base-provider.js';\n\n// Providers\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig,\n SimpleTokenResolver,\n type SimpleTokenResolverConfig,\n JWTAuthProvider,\n type JWTAuthProviderConfig,\n type JWTPayload,\n JWTTokenResolver,\n type JWTTokenResolverConfig,\n APITokenResolver,\n type APITokenResolverConfig\n} from './auth/providers/index.js';\n\n// Note: OAuth and API Key providers can be added in the future if needed\n// For now, JWT-based auth with embedded or API-resolved tokens covers most use cases\n\n// ============================================================================\n// SHARED: UTILITIES\n// ============================================================================\n\nexport {\n // Errors\n MCPAuthError,\n AuthenticationError,\n TokenResolutionError,\n InvalidTokenError,\n MissingCredentialsError,\n ConfigurationError,\n RateLimitError,\n ServerPoolError,\n TransportError,\n ValidationError,\n isMCPAuthError,\n isAuthenticationError,\n isTokenResolutionError,\n isRateLimitError,\n formatErrorForClient,\n \n // Logger\n Logger,\n LogLevel,\n defaultLogger,\n createLogger,\n sanitizeForLogging,\n \n // Validation\n validateNonEmptyString,\n validateUrl,\n validatePositiveNumber,\n validatePort,\n validateEnum,\n validateObject,\n validateFunction,\n validateRequiredFields,\n validateTransportConfig,\n validateRateLimitConfig,\n validateLoggingConfig,\n validatePoolingConfig,\n sanitizeString,\n validateUserId,\n validateResourceType,\n validateAccessToken\n} from './utils/index.js';\n\n// Re-export types for convenience\nexport type { LogEntry } from './utils/logger.js';\n\n// ============================================================================\n// USAGE EXAMPLES\n// ============================================================================\n\n/**\n * @example Server Wrapping Pattern (MCP-Level Auth)\n * ```typescript\n * import { wrapServer, JWTAuthProvider, DatabaseTokenResolver } from '@prmichaelsen/mcp-auth';\n * import { createServer as createInstagramServer } from '@prmichaelsen/instagram-mcp';\n *\n * const wrapped = wrapServer({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken, userId),\n * authProvider: new JWTAuthProvider({ jwtSecret: process.env.JWT_SECRET }),\n * tokenResolver: new DatabaseTokenResolver({ database: {...} }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n *\n * await wrapped.start();\n * ```\n *\n * @example Tool-Level Auth Pattern\n * ```typescript\n * import { AuthenticatedMCPServer, withAuth, EnvAuthProvider } from '@prmichaelsen/mcp-auth';\n *\n * const server = new AuthenticatedMCPServer({\n * name: 'my-server',\n * authProvider: new EnvAuthProvider(),\n * tokenResolver: new SimpleTokenResolver({ tokenEnvVar: 'API_TOKEN' }),\n * resourceType: 'myapi',\n * transport: { type: 'stdio' }\n * });\n *\n * server.registerTool('get_data', withAuth(async (args, accessToken, userId) => {\n * const client = new MyAPIClient(accessToken);\n * return client.getData(args);\n * }));\n *\n * await server.start();\n * ```\n */\n"],
|
|
5
|
+
"mappings": "AAkBA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAQP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OAEK;AAiCP,SAAS,wBAAwB;AAGjC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAGA;AAAA,EAEA;AAAA,OAEK;AASP;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/wrapper/server-wrapper.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Authenticated server wrapper implementation\n * \n * Wraps MCP servers with authentication and multi-tenancy support.\n * Uses ephemeral instances by default for security.\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { ServerWrapperConfig, NormalizedServerWrapperConfig } from './config.js';\nimport type { RequestContext } from '../types.js';\nimport { \n AuthenticationError, \n TokenResolutionError,\n ConfigurationError,\n TransportError\n} from '../utils/errors.js';\nimport { createLogger, type Logger } from '../utils/logger.js';\nimport {\n validateRequiredFields,\n validateResourceType,\n validateUserId,\n validateAccessToken,\n validateTransportConfig\n} from '../utils/validation.js';\n\n/**\n * Server instance metadata (for pooled mode)\n */\ninterface ServerInstance {\n server: Server;\n accessToken: string;\n userId: string;\n createdAt: number;\n lastUsed: number;\n}\n\n/**\n * Authenticated server wrapper\n * \n * Wraps an MCP server with authentication, automatically handling:\n * - Request authentication via AuthProvider\n * - Token resolution via ResourceTokenResolver\n * - Per-user server instance creation (ephemeral or pooled)\n * - Transport management (stdio, SSE, HTTP)\n * \n * @example\n * ```typescript\n * const wrapper = new AuthenticatedServerWrapper({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken),\n * authProvider: new JWTAuthProvider({ ... }),\n * tokenResolver: new DatabaseTokenResolver({ ... }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n * \n * await wrapper.start();\n * ```\n */\nexport class AuthenticatedServerWrapper {\n private config: NormalizedServerWrapperConfig;\n private logger: Logger;\n private serverPool: Map<string, ServerInstance>;\n private isRunning: boolean = false;\n private cleanupTimer?: NodeJS.Timeout;\n \n constructor(config: ServerWrapperConfig) {\n // Validate configuration\n this.validateConfig(config);\n \n // Normalize configuration with defaults\n this.config = this.normalizeConfig(config);\n \n // Initialize logger\n this.logger = createLogger(this.config.middleware.logging);\n \n // Initialize server pool (only used in pooled mode)\n this.serverPool = new Map();\n \n this.logger.info('AuthenticatedServerWrapper created', {\n name: this.config.name,\n resourceType: this.config.resourceType,\n transport: this.config.transport.type,\n instanceMode: this.config.instanceMode\n });\n }\n \n /**\n * Validate wrapper configuration\n */\n private validateConfig(config: ServerWrapperConfig): void {\n // Validate required fields manually for better type safety\n if (!config.serverFactory) {\n throw new ConfigurationError('serverFactory is required');\n }\n if (!config.authProvider) {\n throw new ConfigurationError('authProvider is required');\n }\n if (!config.tokenResolver) {\n throw new ConfigurationError('tokenResolver is required');\n }\n if (!config.resourceType) {\n throw new ConfigurationError('resourceType is required');\n }\n if (!config.transport) {\n throw new ConfigurationError('transport is required');\n }\n \n validateResourceType(config.resourceType);\n validateTransportConfig(config.transport);\n }\n \n /**\n * Normalize configuration with defaults\n */\n private normalizeConfig(config: ServerWrapperConfig): NormalizedServerWrapperConfig {\n return {\n serverFactory: config.serverFactory,\n authProvider: config.authProvider,\n tokenResolver: config.tokenResolver,\n resourceType: config.resourceType,\n transport: config.transport,\n name: config.name ?? 'mcp-auth-wrapped-server',\n version: config.version ?? '1.0.0',\n instanceMode: config.instanceMode ?? 'ephemeral',\n middleware: {\n rateLimit: config.middleware?.rateLimit,\n logging: config.middleware?.logging ?? { enabled: true, level: 'info' }\n },\n pooling: {\n maxServersPerUser: config.pooling?.maxServersPerUser ?? 1,\n idleTimeoutMs: config.pooling?.idleTimeoutMs ?? 300000,\n maxTotalServers: config.pooling?.maxTotalServers ?? 100\n },\n requestTimeoutMs: config.requestTimeoutMs ?? 30000,\n enableTracing: config.enableTracing ?? false\n };\n }\n \n /**\n * Start the wrapped server\n */\n async start(): Promise<void> {\n if (this.isRunning) {\n throw new ConfigurationError('Server is already running');\n }\n \n this.logger.info('Starting authenticated server wrapper', {\n name: this.config.name,\n transport: this.config.transport.type\n });\n \n // Initialize auth provider\n if (this.config.authProvider.initialize) {\n await this.config.authProvider.initialize();\n this.logger.debug('Auth provider initialized');\n }\n \n // Initialize token resolver\n if (this.config.tokenResolver.initialize) {\n await this.config.tokenResolver.initialize();\n this.logger.debug('Token resolver initialized');\n }\n \n // Start appropriate transport\n switch (this.config.transport.type) {\n case 'stdio':\n await this.startStdioTransport();\n break;\n case 'sse':\n await this.startSSETransport();\n break;\n case 'http':\n await this.startHTTPTransport();\n break;\n default:\n throw new TransportError(`Unsupported transport type: ${this.config.transport.type}`);\n }\n \n this.isRunning = true;\n \n this.logger.info('Server wrapper started successfully', {\n name: this.config.name,\n transport: this.config.transport.type,\n port: this.config.transport.port\n });\n }\n \n /**\n * Stop the wrapped server\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n \n this.logger.info('Stopping server wrapper');\n \n // Clear cleanup timer\n if (this.cleanupTimer) {\n clearTimeout(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n \n // Close all pooled servers\n if (this.config.instanceMode === 'pooled') {\n for (const [userId, instance] of this.serverPool.entries()) {\n try {\n await instance.server.close();\n this.logger.debug('Closed pooled server instance', { userId });\n } catch (error) {\n this.logger.error('Error closing server instance', error as Error, { userId });\n }\n }\n this.serverPool.clear();\n }\n \n // Cleanup auth provider\n if (this.config.authProvider.cleanup) {\n await this.config.authProvider.cleanup();\n this.logger.debug('Auth provider cleaned up');\n }\n \n // Cleanup token resolver\n if (this.config.tokenResolver.cleanup) {\n await this.config.tokenResolver.cleanup();\n this.logger.debug('Token resolver cleaned up');\n }\n \n this.isRunning = false;\n \n this.logger.info('Server wrapper stopped');\n }\n \n /**\n * Handle incoming MCP request with authentication\n */\n private async handleRequest(request: any, context: RequestContext): Promise<any> {\n const requestId = context.requestId ?? `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n const requestLogger = this.logger.child({ requestId });\n \n try {\n requestLogger.debug('Handling request', {\n transport: context.transport,\n hasHeaders: !!context.headers\n });\n \n // 1. Authenticate request\n const authResult = await this.config.authProvider.authenticate(context);\n \n if (!authResult.authenticated || !authResult.userId) {\n requestLogger.warn('Authentication failed', {\n error: authResult.error\n });\n throw new AuthenticationError(authResult.error || 'Authentication failed');\n }\n \n const userId = validateUserId(authResult.userId);\n \n requestLogger.debug('Authentication successful', { userId });\n \n // 2. Resolve resource token\n const accessToken = await this.config.tokenResolver.resolveToken(\n userId,\n this.config.resourceType\n );\n \n if (!accessToken) {\n requestLogger.warn('Token resolution failed', {\n userId,\n resourceType: this.config.resourceType\n });\n throw new TokenResolutionError(userId, this.config.resourceType);\n }\n \n validateAccessToken(accessToken);\n \n requestLogger.debug('Token resolved', {\n userId,\n resourceType: this.config.resourceType,\n tokenLength: accessToken.length\n });\n \n // 3. Get server instance (ephemeral or pooled)\n const server = await this.getServerInstance(userId, accessToken);\n \n // 4. Forward request to server\n // Note: This is a simplified version. Actual implementation would need\n // to properly handle MCP protocol messages\n requestLogger.debug('Forwarding request to server instance', { userId });\n \n // TODO: Implement actual MCP request forwarding\n // For now, this is a placeholder\n const response = { success: true, userId, resourceType: this.config.resourceType };\n \n requestLogger.info('Request handled successfully', {\n userId,\n resourceType: this.config.resourceType\n });\n \n return response;\n \n } catch (error) {\n requestLogger.error('Request handling failed', error as Error);\n throw error;\n }\n }\n \n /**\n * Get server instance (ephemeral or from pool)\n */\n private async getServerInstance(userId: string, accessToken: string): Promise<Server> {\n if (this.config.instanceMode === 'ephemeral') {\n // Create new server instance for each request (recommended)\n this.logger.debug('Creating ephemeral server instance', { userId });\n return await this.config.serverFactory(accessToken, userId);\n }\n \n // Pooled mode\n return await this.getPooledServerInstance(userId, accessToken);\n }\n \n /**\n * Get or create pooled server instance\n */\n private async getPooledServerInstance(userId: string, accessToken: string): Promise<Server> {\n // Check if we have a cached server instance\n if (this.serverPool.has(userId)) {\n const instance = this.serverPool.get(userId)!;\n \n // Check if token changed (user rotated token)\n if (instance.accessToken !== accessToken) {\n this.logger.info('Token changed, recreating server instance', { userId });\n await instance.server.close();\n this.serverPool.delete(userId);\n } else {\n // Reuse existing instance\n instance.lastUsed = Date.now();\n this.logger.debug('Reusing pooled server instance', { userId });\n return instance.server;\n }\n }\n \n // Check pool size limit\n if (this.serverPool.size >= this.config.pooling.maxTotalServers) {\n this.logger.warn('Server pool limit reached, evicting oldest instance', {\n poolSize: this.serverPool.size,\n maxTotal: this.config.pooling.maxTotalServers\n });\n await this.evictOldestInstance();\n }\n \n // Create new server instance\n this.logger.info('Creating new pooled server instance', { userId });\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Add to pool\n this.serverPool.set(userId, {\n server,\n accessToken,\n userId,\n createdAt: Date.now(),\n lastUsed: Date.now()\n });\n \n // Schedule cleanup if not already scheduled\n if (!this.cleanupTimer) {\n this.scheduleCleanup();\n }\n \n return server;\n }\n \n /**\n * Evict oldest server instance from pool\n */\n private async evictOldestInstance(): Promise<void> {\n let oldestUserId: string | null = null;\n let oldestTime = Infinity;\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (instance.lastUsed < oldestTime) {\n oldestTime = instance.lastUsed;\n oldestUserId = userId;\n }\n }\n \n if (oldestUserId) {\n const instance = this.serverPool.get(oldestUserId)!;\n await instance.server.close();\n this.serverPool.delete(oldestUserId);\n \n this.logger.debug('Evicted oldest server instance', {\n userId: oldestUserId,\n age: Date.now() - instance.createdAt\n });\n }\n }\n \n /**\n * Schedule cleanup of idle server instances\n */\n private scheduleCleanup(): void {\n const timeout = this.config.pooling.idleTimeoutMs;\n \n this.cleanupTimer = setTimeout(async () => {\n const now = Date.now();\n const toRemove: string[] = [];\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (now - instance.lastUsed > timeout) {\n toRemove.push(userId);\n }\n }\n \n for (const userId of toRemove) {\n const instance = this.serverPool.get(userId)!;\n try {\n await instance.server.close();\n this.serverPool.delete(userId);\n \n this.logger.debug('Cleaned up idle server instance', {\n userId,\n idleTime: now - instance.lastUsed\n });\n } catch (error) {\n this.logger.error('Error cleaning up server instance', error as Error, { userId });\n }\n }\n \n // Reschedule if pool is not empty\n if (this.serverPool.size > 0) {\n this.scheduleCleanup();\n } else {\n this.cleanupTimer = undefined;\n }\n }, timeout);\n }\n \n /**\n * Start stdio transport (single-user mode)\n */\n private async startStdioTransport(): Promise<void> {\n this.logger.info('Starting stdio transport');\n \n // For stdio, we use environment variable for token\n const envVar = `${this.config.resourceType.toUpperCase()}_ACCESS_TOKEN`;\n const accessToken = process.env[envVar];\n \n if (!accessToken) {\n throw new ConfigurationError(\n `${envVar} environment variable required for stdio mode`\n );\n }\n \n const userId = 'stdio-user';\n \n // Create server instance\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Connect to stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n \n this.logger.info('Stdio transport started', { userId });\n }\n \n /**\n * Start SSE transport (multi-user mode)\n */\n private async startSSETransport(): Promise<void> {\n this.logger.info('Starting SSE transport', {\n port: this.config.transport.port,\n basePath: this.config.transport.basePath\n });\n \n // Import express dynamically\n const express = await import('express');\n const app = express.default();\n \n // Enable JSON parsing\n app.use(express.json());\n \n // Enable CORS if configured\n if (this.config.transport.cors) {\n const cors = await import('cors');\n app.use(cors.default({\n origin: this.config.transport.corsOrigin || '*'\n }));\n }\n \n const basePath = this.config.transport.basePath || '/mcp';\n \n // SSE endpoint for MCP messages\n app.post(`${basePath}/message`, async (req: any, res: any) => {\n try {\n const context: RequestContext = {\n headers: req.headers as Record<string, string>,\n transport: 'sse',\n timestamp: new Date(),\n requestId: req.headers['x-request-id'] as string | undefined\n };\n \n const result = await this.handleRequest(req.body, context);\n res.json(result);\n \n } catch (error) {\n this.logger.error('SSE request failed', error as Error);\n \n if (error instanceof AuthenticationError || error instanceof TokenResolutionError) {\n res.status(error.statusCode).json({\n error: error.message,\n code: error.code\n });\n } else {\n res.status(500).json({\n error: 'Internal server error',\n code: 'INTERNAL_ERROR'\n });\n }\n }\n });\n \n // Health check endpoint\n app.get(`${basePath}/health`, (req: any, res: any) => {\n res.json({\n status: 'healthy',\n name: this.config.name,\n version: this.config.version,\n resourceType: this.config.resourceType,\n instanceMode: this.config.instanceMode,\n poolSize: this.serverPool.size\n });\n });\n \n // Start server\n const port = this.config.transport.port || 3000;\n const host = this.config.transport.host || '0.0.0.0';\n \n await new Promise<void>((resolve) => {\n app.listen(port, host, () => {\n this.logger.info('SSE transport listening', {\n host,\n port,\n basePath,\n url: `http://${host}:${port}${basePath}`\n });\n resolve();\n });\n });\n }\n \n /**\n * Start HTTP transport (multi-user mode)\n */\n private async startHTTPTransport(): Promise<void> {\n this.logger.info('Starting HTTP transport', {\n port: this.config.transport.port\n });\n \n // HTTP transport is similar to SSE but with different endpoint structure\n // For now, delegate to SSE implementation\n await this.startSSETransport();\n }\n \n /**\n * Get server pool statistics\n */\n getPoolStats(): {\n size: number;\n instances: Array<{\n userId: string;\n createdAt: number;\n lastUsed: number;\n age: number;\n idleTime: number;\n }>;\n } {\n const now = Date.now();\n const instances = Array.from(this.serverPool.entries()).map(([userId, instance]) => ({\n userId,\n createdAt: instance.createdAt,\n lastUsed: instance.lastUsed,\n age: now - instance.createdAt,\n idleTime: now - instance.lastUsed\n }));\n \n return {\n size: this.serverPool.size,\n instances\n };\n }\n \n /**\n * Check if server is running\n */\n isServerRunning(): boolean {\n return this.isRunning;\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Authenticated server wrapper implementation\n *\n * Wraps MCP servers with authentication and multi-tenancy support.\n * Uses ephemeral instances by default for security.\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { ServerWrapperConfig, NormalizedServerWrapperConfig } from './config.js';\nimport type { RequestContext } from '../types.js';\nimport { \n AuthenticationError, \n TokenResolutionError,\n ConfigurationError,\n TransportError\n} from '../utils/errors.js';\nimport { createLogger, type Logger } from '../utils/logger.js';\nimport {\n validateRequiredFields,\n validateResourceType,\n validateUserId,\n validateAccessToken,\n validateTransportConfig\n} from '../utils/validation.js';\n\n/**\n * Server instance metadata (for pooled mode)\n */\ninterface ServerInstance {\n server: Server;\n accessToken: string;\n userId: string;\n createdAt: number;\n lastUsed: number;\n}\n\n/**\n * Authenticated server wrapper\n * \n * Wraps an MCP server with authentication, automatically handling:\n * - Request authentication via AuthProvider\n * - Token resolution via ResourceTokenResolver\n * - Per-user server instance creation (ephemeral or pooled)\n * - Transport management (stdio, SSE, HTTP)\n * \n * @example\n * ```typescript\n * const wrapper = new AuthenticatedServerWrapper({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken),\n * authProvider: new JWTAuthProvider({ ... }),\n * tokenResolver: new DatabaseTokenResolver({ ... }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n * \n * await wrapper.start();\n * ```\n */\nexport class AuthenticatedServerWrapper {\n private config: NormalizedServerWrapperConfig;\n private logger: Logger;\n private serverPool: Map<string, ServerInstance>;\n private isRunning: boolean = false;\n private cleanupTimer?: NodeJS.Timeout;\n \n constructor(config: ServerWrapperConfig) {\n // Validate configuration\n this.validateConfig(config);\n \n // Normalize configuration with defaults\n this.config = this.normalizeConfig(config);\n \n // Initialize logger\n this.logger = createLogger(this.config.middleware.logging);\n \n // Initialize server pool (only used in pooled mode)\n this.serverPool = new Map();\n \n this.logger.info('AuthenticatedServerWrapper created', {\n name: this.config.name,\n resourceType: this.config.resourceType,\n transport: this.config.transport.type,\n instanceMode: this.config.instanceMode\n });\n }\n \n /**\n * Validate wrapper configuration\n */\n private validateConfig(config: ServerWrapperConfig): void {\n // Validate required fields manually for better type safety\n if (!config.serverFactory) {\n throw new ConfigurationError('serverFactory is required');\n }\n if (!config.authProvider) {\n throw new ConfigurationError('authProvider is required');\n }\n if (!config.tokenResolver) {\n throw new ConfigurationError('tokenResolver is required');\n }\n if (!config.resourceType) {\n throw new ConfigurationError('resourceType is required');\n }\n if (!config.transport) {\n throw new ConfigurationError('transport is required');\n }\n \n validateResourceType(config.resourceType);\n validateTransportConfig(config.transport);\n }\n \n /**\n * Normalize configuration with defaults\n */\n private normalizeConfig(config: ServerWrapperConfig): NormalizedServerWrapperConfig {\n return {\n serverFactory: config.serverFactory,\n authProvider: config.authProvider,\n tokenResolver: config.tokenResolver,\n resourceType: config.resourceType,\n transport: config.transport,\n name: config.name ?? 'mcp-auth-wrapped-server',\n version: config.version ?? '1.0.0',\n instanceMode: config.instanceMode ?? 'ephemeral',\n middleware: {\n rateLimit: config.middleware?.rateLimit,\n logging: config.middleware?.logging ?? { enabled: true, level: 'info' }\n },\n pooling: {\n maxServersPerUser: config.pooling?.maxServersPerUser ?? 1,\n idleTimeoutMs: config.pooling?.idleTimeoutMs ?? 300000,\n maxTotalServers: config.pooling?.maxTotalServers ?? 100\n },\n requestTimeoutMs: config.requestTimeoutMs ?? 30000,\n enableTracing: config.enableTracing ?? false\n };\n }\n \n /**\n * Start the wrapped server\n */\n async start(): Promise<void> {\n if (this.isRunning) {\n throw new ConfigurationError('Server is already running');\n }\n \n this.logger.info('Starting authenticated server wrapper', {\n name: this.config.name,\n transport: this.config.transport.type\n });\n \n // Initialize auth provider\n if (this.config.authProvider.initialize) {\n await this.config.authProvider.initialize();\n this.logger.debug('Auth provider initialized');\n }\n \n // Initialize token resolver\n if (this.config.tokenResolver.initialize) {\n await this.config.tokenResolver.initialize();\n this.logger.debug('Token resolver initialized');\n }\n \n // Start appropriate transport\n switch (this.config.transport.type) {\n case 'stdio':\n await this.startStdioTransport();\n break;\n case 'sse':\n await this.startSSETransport();\n break;\n case 'http':\n await this.startHTTPTransport();\n break;\n default:\n throw new TransportError(`Unsupported transport type: ${this.config.transport.type}`);\n }\n \n this.isRunning = true;\n \n this.logger.info('Server wrapper started successfully', {\n name: this.config.name,\n transport: this.config.transport.type,\n port: this.config.transport.port\n });\n }\n \n /**\n * Stop the wrapped server\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n \n this.logger.info('Stopping server wrapper');\n \n // Clear cleanup timer\n if (this.cleanupTimer) {\n clearTimeout(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n \n // Close all pooled servers\n if (this.config.instanceMode === 'pooled') {\n for (const [userId, instance] of this.serverPool.entries()) {\n try {\n await instance.server.close();\n this.logger.debug('Closed pooled server instance', { userId });\n } catch (error) {\n this.logger.error('Error closing server instance', error as Error, { userId });\n }\n }\n this.serverPool.clear();\n }\n \n // Cleanup auth provider\n if (this.config.authProvider.cleanup) {\n await this.config.authProvider.cleanup();\n this.logger.debug('Auth provider cleaned up');\n }\n \n // Cleanup token resolver\n if (this.config.tokenResolver.cleanup) {\n await this.config.tokenResolver.cleanup();\n this.logger.debug('Token resolver cleaned up');\n }\n \n this.isRunning = false;\n \n this.logger.info('Server wrapper stopped');\n }\n \n /**\n * Handle incoming MCP request with authentication\n */\n private async handleRequest(request: any, context: RequestContext): Promise<any> {\n const requestId = context.requestId ?? `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n const requestLogger = this.logger.child({ requestId });\n \n try {\n requestLogger.debug('Handling request', {\n transport: context.transport,\n hasHeaders: !!context.headers\n });\n \n // 1. Authenticate request\n const authResult = await this.config.authProvider.authenticate(context);\n \n if (!authResult.authenticated || !authResult.userId) {\n requestLogger.warn('Authentication failed', {\n error: authResult.error\n });\n throw new AuthenticationError(authResult.error || 'Authentication failed');\n }\n \n const userId = validateUserId(authResult.userId);\n \n requestLogger.debug('Authentication successful', { userId });\n \n // 2. Resolve resource token\n const accessToken = await this.config.tokenResolver.resolveToken(\n userId,\n this.config.resourceType\n );\n \n if (!accessToken) {\n requestLogger.warn('Token resolution failed', {\n userId,\n resourceType: this.config.resourceType\n });\n throw new TokenResolutionError(userId, this.config.resourceType);\n }\n \n validateAccessToken(accessToken);\n \n requestLogger.debug('Token resolved', {\n userId,\n resourceType: this.config.resourceType,\n tokenLength: accessToken.length\n });\n \n // 3. Get server instance (ephemeral or pooled)\n const server = await this.getServerInstance(userId, accessToken);\n \n // 4. Forward request to server\n // Note: This is a simplified version. Actual implementation would need\n // to properly handle MCP protocol messages\n requestLogger.debug('Forwarding request to server instance', { userId });\n \n // TODO: Implement actual MCP request forwarding\n // For now, this is a placeholder\n const response = { success: true, userId, resourceType: this.config.resourceType };\n \n requestLogger.info('Request handled successfully', {\n userId,\n resourceType: this.config.resourceType\n });\n \n return response;\n \n } catch (error) {\n requestLogger.error('Request handling failed', error as Error);\n throw error;\n }\n }\n \n /**\n * Get server instance (ephemeral or from pool)\n */\n private async getServerInstance(userId: string, accessToken: string): Promise<Server> {\n if (this.config.instanceMode === 'ephemeral') {\n // Create new server instance for each request (recommended)\n this.logger.debug('Creating ephemeral server instance', { userId });\n return await this.config.serverFactory(accessToken, userId);\n }\n \n // Pooled mode\n return await this.getPooledServerInstance(userId, accessToken);\n }\n \n /**\n * Get or create pooled server instance\n */\n private async getPooledServerInstance(userId: string, accessToken: string): Promise<Server> {\n // Check if we have a cached server instance\n if (this.serverPool.has(userId)) {\n const instance = this.serverPool.get(userId)!;\n \n // Check if token changed (user rotated token)\n if (instance.accessToken !== accessToken) {\n this.logger.info('Token changed, recreating server instance', { userId });\n await instance.server.close();\n this.serverPool.delete(userId);\n } else {\n // Reuse existing instance\n instance.lastUsed = Date.now();\n this.logger.debug('Reusing pooled server instance', { userId });\n return instance.server;\n }\n }\n \n // Check pool size limit\n if (this.serverPool.size >= this.config.pooling.maxTotalServers) {\n this.logger.warn('Server pool limit reached, evicting oldest instance', {\n poolSize: this.serverPool.size,\n maxTotal: this.config.pooling.maxTotalServers\n });\n await this.evictOldestInstance();\n }\n \n // Create new server instance\n this.logger.info('Creating new pooled server instance', { userId });\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Add to pool\n this.serverPool.set(userId, {\n server,\n accessToken,\n userId,\n createdAt: Date.now(),\n lastUsed: Date.now()\n });\n \n // Schedule cleanup if not already scheduled\n if (!this.cleanupTimer) {\n this.scheduleCleanup();\n }\n \n return server;\n }\n \n /**\n * Evict oldest server instance from pool\n */\n private async evictOldestInstance(): Promise<void> {\n let oldestUserId: string | null = null;\n let oldestTime = Infinity;\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (instance.lastUsed < oldestTime) {\n oldestTime = instance.lastUsed;\n oldestUserId = userId;\n }\n }\n \n if (oldestUserId) {\n const instance = this.serverPool.get(oldestUserId)!;\n await instance.server.close();\n this.serverPool.delete(oldestUserId);\n \n this.logger.debug('Evicted oldest server instance', {\n userId: oldestUserId,\n age: Date.now() - instance.createdAt\n });\n }\n }\n \n /**\n * Schedule cleanup of idle server instances\n */\n private scheduleCleanup(): void {\n const timeout = this.config.pooling.idleTimeoutMs;\n \n this.cleanupTimer = setTimeout(async () => {\n const now = Date.now();\n const toRemove: string[] = [];\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (now - instance.lastUsed > timeout) {\n toRemove.push(userId);\n }\n }\n \n for (const userId of toRemove) {\n const instance = this.serverPool.get(userId)!;\n try {\n await instance.server.close();\n this.serverPool.delete(userId);\n \n this.logger.debug('Cleaned up idle server instance', {\n userId,\n idleTime: now - instance.lastUsed\n });\n } catch (error) {\n this.logger.error('Error cleaning up server instance', error as Error, { userId });\n }\n }\n \n // Reschedule if pool is not empty\n if (this.serverPool.size > 0) {\n this.scheduleCleanup();\n } else {\n this.cleanupTimer = undefined;\n }\n }, timeout);\n }\n \n /**\n * Start stdio transport (single-user mode)\n */\n private async startStdioTransport(): Promise<void> {\n this.logger.info('Starting stdio transport');\n \n // For stdio, we use environment variable for token\n const envVar = `${this.config.resourceType.toUpperCase()}_ACCESS_TOKEN`;\n const accessToken = process.env[envVar];\n \n if (!accessToken) {\n throw new ConfigurationError(\n `${envVar} environment variable required for stdio mode`\n );\n }\n \n const userId = 'stdio-user';\n \n // Create server instance\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Connect to stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n \n this.logger.info('Stdio transport started', { userId });\n }\n \n /**\n * Start SSE transport (multi-user mode)\n */\n private async startSSETransport(): Promise<void> {\n this.logger.info('Starting SSE transport', {\n port: this.config.transport.port,\n basePath: this.config.transport.basePath\n });\n \n // Import express dynamically (optional dependency)\n // @ts-ignore - Dynamic import of optional dependency\n const express = await import('express');\n const app = express.default();\n \n // Enable JSON parsing\n app.use(express.json());\n \n // Enable CORS if configured\n if (this.config.transport.cors) {\n // @ts-ignore - Dynamic import of optional dependency\n const cors = await import('cors');\n app.use(cors.default({\n origin: this.config.transport.corsOrigin || '*'\n }));\n }\n \n const basePath = this.config.transport.basePath || '/mcp';\n \n // SSE endpoint for MCP messages\n app.post(`${basePath}/message`, async (req: any, res: any) => {\n try {\n const context: RequestContext = {\n headers: req.headers as Record<string, string>,\n transport: 'sse',\n timestamp: new Date(),\n requestId: req.headers['x-request-id'] as string | undefined\n };\n \n const result = await this.handleRequest(req.body, context);\n res.json(result);\n \n } catch (error) {\n this.logger.error('SSE request failed', error as Error);\n \n if (error instanceof AuthenticationError || error instanceof TokenResolutionError) {\n res.status(error.statusCode).json({\n error: error.message,\n code: error.code\n });\n } else {\n res.status(500).json({\n error: 'Internal server error',\n code: 'INTERNAL_ERROR'\n });\n }\n }\n });\n \n // Health check endpoint\n app.get(`${basePath}/health`, (req: any, res: any) => {\n res.json({\n status: 'healthy',\n name: this.config.name,\n version: this.config.version,\n resourceType: this.config.resourceType,\n instanceMode: this.config.instanceMode,\n poolSize: this.serverPool.size\n });\n });\n \n // Start server\n const port = this.config.transport.port || 3000;\n const host = this.config.transport.host || '0.0.0.0';\n \n await new Promise<void>((resolve) => {\n app.listen(port, host, () => {\n this.logger.info('SSE transport listening', {\n host,\n port,\n basePath,\n url: `http://${host}:${port}${basePath}`\n });\n resolve();\n });\n });\n }\n \n /**\n * Start HTTP transport (multi-user mode)\n */\n private async startHTTPTransport(): Promise<void> {\n this.logger.info('Starting HTTP transport', {\n port: this.config.transport.port\n });\n \n // HTTP transport is similar to SSE but with different endpoint structure\n // For now, delegate to SSE implementation\n await this.startSSETransport();\n }\n \n /**\n * Get server pool statistics\n */\n getPoolStats(): {\n size: number;\n instances: Array<{\n userId: string;\n createdAt: number;\n lastUsed: number;\n age: number;\n idleTime: number;\n }>;\n } {\n const now = Date.now();\n const instances = Array.from(this.serverPool.entries()).map(([userId, instance]) => ({\n userId,\n createdAt: instance.createdAt,\n lastUsed: instance.lastUsed,\n age: now - instance.createdAt,\n idleTime: now - instance.lastUsed\n }));\n \n return {\n size: this.serverPool.size,\n instances\n };\n }\n \n /**\n * Check if server is running\n */\n isServerRunning(): boolean {\n return this.isRunning;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prmichaelsen/mcp-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Authentication and multi-tenancy framework for MCP servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -85,7 +85,9 @@
|
|
|
85
85
|
},
|
|
86
86
|
"optionalDependencies": {
|
|
87
87
|
"@types/jsonwebtoken": "^9.0.0",
|
|
88
|
-
"jsonwebtoken": "^9.0.0"
|
|
88
|
+
"jsonwebtoken": "^9.0.0",
|
|
89
|
+
"express": "^4.21.2",
|
|
90
|
+
"cors": "^2.8.5"
|
|
89
91
|
},
|
|
90
92
|
"publishConfig": {
|
|
91
93
|
"access": "public"
|