@frontmcp/sdk 0.5.1 → 0.6.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 +1 -0
- package/package.json +12 -16
- package/src/adapter/adapter.instance.js +5 -0
- package/src/adapter/adapter.instance.js.map +1 -1
- package/src/auth/authorization/authorization.class.d.ts +1 -4
- package/src/auth/authorization/authorization.class.js +6 -13
- package/src/auth/authorization/authorization.class.js.map +1 -1
- package/src/auth/flows/session.verify.flow.d.ts +1 -0
- package/src/auth/flows/session.verify.flow.js +11 -1
- package/src/auth/flows/session.verify.flow.js.map +1 -1
- package/src/auth/flows/well-known.jwks.flow.js +2 -2
- package/src/auth/flows/well-known.jwks.flow.js.map +1 -1
- package/src/auth/jwks/dev-key-persistence.d.ts +63 -0
- package/src/auth/jwks/dev-key-persistence.js +219 -0
- package/src/auth/jwks/dev-key-persistence.js.map +1 -0
- package/src/auth/jwks/index.d.ts +1 -0
- package/src/auth/jwks/index.js +1 -0
- package/src/auth/jwks/index.js.map +1 -1
- package/src/auth/jwks/jwks.service.d.ts +7 -4
- package/src/auth/jwks/jwks.service.js +81 -12
- package/src/auth/jwks/jwks.service.js.map +1 -1
- package/src/auth/jwks/jwks.types.d.ts +7 -0
- package/src/auth/jwks/jwks.types.js.map +1 -1
- package/src/auth/machine-id.d.ts +5 -0
- package/src/auth/machine-id.js +32 -0
- package/src/auth/machine-id.js.map +1 -0
- package/src/auth/session/index.d.ts +2 -0
- package/src/auth/session/index.js +5 -1
- package/src/auth/session/index.js.map +1 -1
- package/src/auth/session/record/session.base.js +5 -3
- package/src/auth/session/record/session.base.js.map +1 -1
- package/src/auth/session/record/session.stateless.d.ts +2 -2
- package/src/auth/session/record/session.stateless.js +5 -3
- package/src/auth/session/record/session.stateless.js.map +1 -1
- package/src/auth/session/redis-session.store.d.ts +64 -0
- package/src/auth/session/redis-session.store.js +204 -0
- package/src/auth/session/redis-session.store.js.map +1 -0
- package/src/auth/session/session.service.d.ts +0 -2
- package/src/auth/session/session.service.js +1 -7
- package/src/auth/session/session.service.js.map +1 -1
- package/src/auth/session/transport-session.manager.js +3 -5
- package/src/auth/session/transport-session.manager.js.map +1 -1
- package/src/auth/session/transport-session.types.d.ts +4 -0
- package/src/auth/session/transport-session.types.js +4 -3
- package/src/auth/session/transport-session.types.js.map +1 -1
- package/src/auth/session/utils/session-id.utils.d.ts +12 -1
- package/src/auth/session/utils/session-id.utils.js +48 -9
- package/src/auth/session/utils/session-id.utils.js.map +1 -1
- package/src/auth/session/vercel-kv-session.store.d.ts +96 -0
- package/src/auth/session/vercel-kv-session.store.js +216 -0
- package/src/auth/session/vercel-kv-session.store.js.map +1 -0
- package/src/auth/ui/base-layout.d.ts +0 -8
- package/src/auth/ui/base-layout.js +1 -14
- package/src/auth/ui/base-layout.js.map +1 -1
- package/src/auth/ui/index.d.ts +3 -4
- package/src/auth/ui/index.js +10 -11
- package/src/auth/ui/index.js.map +1 -1
- package/src/auth/ui/{htmx-templates.d.ts → templates.d.ts} +5 -6
- package/src/auth/ui/{htmx-templates.js → templates.js} +8 -15
- package/src/auth/ui/templates.js.map +1 -0
- package/src/common/decorators/decorator-utils.js.map +1 -1
- package/src/common/decorators/front-mcp.decorator.js +26 -3
- package/src/common/decorators/front-mcp.decorator.js.map +1 -1
- package/src/common/index.d.ts +0 -1
- package/src/common/index.js +0 -1
- package/src/common/index.js.map +1 -1
- package/src/common/interfaces/adapter.interface.d.ts +6 -0
- package/src/common/interfaces/adapter.interface.js.map +1 -1
- package/src/common/interfaces/execution-context.interface.d.ts +52 -3
- package/src/common/interfaces/execution-context.interface.js +88 -3
- package/src/common/interfaces/execution-context.interface.js.map +1 -1
- package/src/common/interfaces/flow.interface.d.ts +13 -0
- package/src/common/interfaces/flow.interface.js +24 -0
- package/src/common/interfaces/flow.interface.js.map +1 -1
- package/src/common/interfaces/server.interface.d.ts +9 -0
- package/src/common/interfaces/server.interface.js.map +1 -1
- package/src/common/metadata/app.metadata.d.ts +108 -0
- package/src/common/metadata/front-mcp.metadata.d.ts +1341 -2
- package/src/common/metadata/front-mcp.metadata.js +4 -1
- package/src/common/metadata/front-mcp.metadata.js.map +1 -1
- package/src/common/metadata/prompt.metadata.d.ts +4 -0
- package/src/common/metadata/provider.metadata.d.ts +14 -0
- package/src/common/metadata/provider.metadata.js +18 -2
- package/src/common/metadata/provider.metadata.js.map +1 -1
- package/src/common/metadata/resource.metadata.d.ts +8 -0
- package/src/common/metadata/tool-ui.metadata.d.ts +2 -2
- package/src/common/metadata/tool-ui.metadata.js +1 -1
- package/src/common/metadata/tool-ui.metadata.js.map +1 -1
- package/src/common/metadata/tool.metadata.d.ts +5 -1
- package/src/common/metadata/tool.metadata.js.map +1 -1
- package/src/common/migrate/auth-transport.migrate.d.ts +62 -0
- package/src/common/migrate/auth-transport.migrate.js +140 -0
- package/src/common/migrate/auth-transport.migrate.js.map +1 -0
- package/src/common/migrate/index.d.ts +1 -0
- package/src/common/migrate/index.js +6 -0
- package/src/common/migrate/index.js.map +1 -0
- package/src/common/schemas/http-output.schema.d.ts +24 -6
- package/src/common/schemas/index.d.ts +1 -0
- package/src/common/schemas/index.js +1 -0
- package/src/common/schemas/index.js.map +1 -1
- package/src/common/schemas/session-header.schema.d.ts +16 -0
- package/src/common/schemas/session-header.schema.js +42 -0
- package/src/common/schemas/session-header.schema.js.map +1 -0
- package/src/common/tokens/front-mcp.tokens.js +4 -1
- package/src/common/tokens/front-mcp.tokens.js.map +1 -1
- package/src/common/types/options/auth.options.d.ts +233 -3
- package/src/common/types/options/auth.options.js +29 -40
- package/src/common/types/options/auth.options.js.map +1 -1
- package/src/common/types/options/index.d.ts +2 -0
- package/src/common/types/options/index.js +2 -0
- package/src/common/types/options/index.js.map +1 -1
- package/src/common/types/options/redis.options.d.ts +190 -0
- package/src/common/types/options/redis.options.js +191 -0
- package/src/common/types/options/redis.options.js.map +1 -0
- package/src/common/types/options/server-info.options.d.ts +4 -0
- package/src/common/types/options/transport.options.d.ts +148 -0
- package/src/common/types/options/transport.options.js +121 -0
- package/src/common/types/options/transport.options.js.map +1 -0
- package/src/common/utils/global-config.utils.d.ts +36 -0
- package/src/common/utils/global-config.utils.js +44 -0
- package/src/common/utils/global-config.utils.js.map +1 -0
- package/src/common/utils/index.d.ts +1 -0
- package/src/common/utils/index.js +1 -0
- package/src/common/utils/index.js.map +1 -1
- package/src/completion/flows/complete.flow.d.ts +6 -8
- package/src/context/frontmcp-context-storage.d.ts +94 -0
- package/src/context/frontmcp-context-storage.js +183 -0
- package/src/context/frontmcp-context-storage.js.map +1 -0
- package/src/context/frontmcp-context.d.ts +269 -0
- package/src/context/frontmcp-context.js +360 -0
- package/src/context/frontmcp-context.js.map +1 -0
- package/src/context/frontmcp-context.provider.d.ts +43 -0
- package/src/context/frontmcp-context.provider.js +61 -0
- package/src/context/frontmcp-context.provider.js.map +1 -0
- package/src/context/index.d.ts +34 -0
- package/src/context/index.js +64 -0
- package/src/context/index.js.map +1 -0
- package/src/context/request-context-storage.d.ts +89 -0
- package/src/context/request-context-storage.js +183 -0
- package/src/context/request-context-storage.js.map +1 -0
- package/src/context/request-context.d.ts +184 -0
- package/src/context/request-context.js +209 -0
- package/src/context/request-context.js.map +1 -0
- package/src/context/request-context.provider.d.ts +37 -0
- package/src/context/request-context.provider.js +51 -0
- package/src/context/request-context.provider.js.map +1 -0
- package/src/context/session-key.provider.d.ts +45 -0
- package/src/context/session-key.provider.js +65 -0
- package/src/context/session-key.provider.js.map +1 -0
- package/src/context/trace-context.d.ts +43 -0
- package/src/context/trace-context.js +142 -0
- package/src/context/trace-context.js.map +1 -0
- package/src/errors/index.d.ts +1 -1
- package/src/errors/index.js +4 -1
- package/src/errors/index.js.map +1 -1
- package/src/errors/mcp.error.d.ts +16 -0
- package/src/errors/mcp.error.js +29 -1
- package/src/errors/mcp.error.js.map +1 -1
- package/src/flows/flow.instance.d.ts +16 -0
- package/src/flows/flow.instance.js +166 -80
- package/src/flows/flow.instance.js.map +1 -1
- package/src/flows/flow.registry.d.ts +5 -0
- package/src/flows/flow.registry.js +45 -3
- package/src/flows/flow.registry.js.map +1 -1
- package/src/front-mcp/front-mcp.d.ts +12 -0
- package/src/front-mcp/front-mcp.js +22 -3
- package/src/front-mcp/front-mcp.js.map +1 -1
- package/src/front-mcp/front-mcp.providers.d.ts +474 -1
- package/src/front-mcp/front-mcp.providers.js +2 -1
- package/src/front-mcp/front-mcp.providers.js.map +1 -1
- package/src/front-mcp/index.d.ts +1 -0
- package/src/front-mcp/index.js +3 -0
- package/src/front-mcp/index.js.map +1 -1
- package/src/front-mcp/serverless-handler.d.ts +28 -0
- package/src/front-mcp/serverless-handler.js +61 -0
- package/src/front-mcp/serverless-handler.js.map +1 -0
- package/src/hooks/hooks.utils.d.ts +1 -1
- package/src/hooks/hooks.utils.js +10 -3
- package/src/hooks/hooks.utils.js.map +1 -1
- package/src/index.d.ts +9 -5
- package/src/index.js +21 -1
- package/src/index.js.map +1 -1
- package/src/logger/instances/instance.logger.js +0 -1
- package/src/logger/instances/instance.logger.js.map +1 -1
- package/src/logging/flows/set-level.flow.d.ts +6 -8
- package/src/notification/notification.service.js +5 -1
- package/src/notification/notification.service.js.map +1 -1
- package/src/prompt/flows/get-prompt.flow.d.ts +14 -8
- package/src/prompt/flows/prompts-list.flow.d.ts +8 -7
- package/src/provider/provider.registry.d.ts +97 -5
- package/src/provider/provider.registry.js +306 -9
- package/src/provider/provider.registry.js.map +1 -1
- package/src/provider/provider.types.d.ts +21 -3
- package/src/provider/provider.types.js.map +1 -1
- package/src/resource/flows/read-resource.flow.d.ts +8 -9
- package/src/resource/flows/resource-templates-list.flow.d.ts +8 -7
- package/src/resource/flows/resources-list.flow.d.ts +8 -7
- package/src/resource/flows/subscribe-resource.flow.d.ts +6 -8
- package/src/resource/flows/unsubscribe-resource.flow.d.ts +6 -8
- package/src/scope/flows/http.request.flow.js +43 -7
- package/src/scope/flows/http.request.flow.js.map +1 -1
- package/src/scope/scope.instance.js +12 -5
- package/src/scope/scope.instance.js.map +1 -1
- package/src/server/adapters/base.host.adapter.d.ts +9 -0
- package/src/server/adapters/base.host.adapter.js.map +1 -1
- package/src/server/adapters/express.host.adapter.d.ts +12 -0
- package/src/server/adapters/express.host.adapter.js +21 -1
- package/src/server/adapters/express.host.adapter.js.map +1 -1
- package/src/server/server.instance.d.ts +3 -0
- package/src/server/server.instance.js +14 -7
- package/src/server/server.instance.js.map +1 -1
- package/src/store/adapters/store.vercel-kv.adapter.d.ts +86 -0
- package/src/store/adapters/store.vercel-kv.adapter.js +155 -0
- package/src/store/adapters/store.vercel-kv.adapter.js.map +1 -0
- package/src/store/index.d.ts +2 -0
- package/src/store/index.js +2 -0
- package/src/store/index.js.map +1 -1
- package/src/store/store.factory.d.ts +86 -0
- package/src/store/store.factory.js +194 -0
- package/src/store/store.factory.js.map +1 -0
- package/src/tool/flows/call-tool.flow.d.ts +38 -19
- package/src/tool/flows/call-tool.flow.js +240 -194
- package/src/tool/flows/call-tool.flow.js.map +1 -1
- package/src/tool/flows/tools-list.flow.d.ts +14 -17
- package/src/tool/flows/tools-list.flow.js +84 -33
- package/src/tool/flows/tools-list.flow.js.map +1 -1
- package/src/tool/tool.instance.d.ts +1 -4
- package/src/tool/ui/index.d.ts +4 -4
- package/src/tool/ui/index.js +4 -4
- package/src/tool/ui/index.js.map +1 -1
- package/src/tool/ui/platform-adapters.d.ts +2 -2
- package/src/tool/ui/platform-adapters.js +3 -3
- package/src/tool/ui/platform-adapters.js.map +1 -1
- package/src/tool/ui/template-helpers.d.ts +5 -7
- package/src/tool/ui/template-helpers.js +9 -26
- package/src/tool/ui/template-helpers.js.map +1 -1
- package/src/tool/ui/ui-resource.handler.d.ts +1 -1
- package/src/tool/ui/ui-resource.handler.js +5 -5
- package/src/tool/ui/ui-resource.handler.js.map +1 -1
- package/src/transport/adapters/transport.streamable-http.adapter.js +1 -0
- package/src/transport/adapters/transport.streamable-http.adapter.js.map +1 -1
- package/src/transport/flows/handle.sse.flow.js +9 -2
- package/src/transport/flows/handle.sse.flow.js.map +1 -1
- package/src/transport/flows/handle.streamable-http.flow.js +63 -6
- package/src/transport/flows/handle.streamable-http.flow.js.map +1 -1
- package/src/transport/mcp-handlers/complete-request.handler.d.ts +4 -15
- package/src/transport/mcp-handlers/get-prompt-request.handler.d.ts +5 -15
- package/src/transport/mcp-handlers/index.d.ts +67 -195
- package/src/transport/mcp-handlers/initialize-request.handler.js +12 -2
- package/src/transport/mcp-handlers/initialize-request.handler.js.map +1 -1
- package/src/transport/mcp-handlers/list-prompts-request.handler.d.ts +5 -15
- package/src/transport/mcp-handlers/list-resource-templates-request.handler.d.ts +5 -15
- package/src/transport/mcp-handlers/list-resources-request.handler.d.ts +5 -15
- package/src/transport/mcp-handlers/list-tools-request.handler.d.ts +5 -15
- package/src/transport/mcp-handlers/logging-set-level-request.handler.d.ts +3 -14
- package/src/transport/mcp-handlers/read-resource-request.handler.d.ts +4 -15
- package/src/transport/mcp-handlers/subscribe-request.handler.d.ts +3 -14
- package/src/transport/mcp-handlers/unsubscribe-request.handler.d.ts +3 -14
- package/src/transport/transport.registry.d.ts +72 -4
- package/src/transport/transport.registry.js +342 -11
- package/src/transport/transport.registry.js.map +1 -1
- package/src/auth/ui/htmx-templates.js.map +0 -1
- package/src/common/providers/session.provider.d.ts +0 -13
- package/src/common/providers/session.provider.js +0 -27
- package/src/common/providers/session.provider.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport-session.manager.js","sourceRoot":"","sources":["../../../../src/auth/session/transport-session.manager.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/transport-session.manager.ts\n\nimport { randomUUID } from 'crypto';\nimport {\n TransportSession,\n TransportProtocol,\n SessionJwtPayload,\n StatelessSessionJwtPayload,\n StoredSession,\n SessionStore,\n SessionStorageConfig,\n TransportState,\n EncryptedBlob,\n} from './transport-session.types';\nimport { encryptJson } from './utils/session-id.utils';\nimport { encryptAesGcm, decryptAesGcm, hkdfSha256 } from './session.crypto';\nimport { getMachineId } from '../authorization/authorization.class';\n\n/**\n * In-memory session store implementation\n */\nexport class InMemorySessionStore implements SessionStore {\n private readonly sessions = new Map<string, StoredSession>();\n\n async get(sessionId: string): Promise<StoredSession | null> {\n const stored = this.sessions.get(sessionId);\n if (!stored) return null;\n\n // Check expiration\n if (stored.session.expiresAt && stored.session.expiresAt < Date.now()) {\n this.sessions.delete(sessionId);\n return null;\n }\n\n // Update last accessed\n stored.lastAccessedAt = Date.now();\n return stored;\n }\n\n async set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void> {\n if (ttlMs) {\n session.session.expiresAt = Date.now() + ttlMs;\n }\n this.sessions.set(sessionId, session);\n }\n\n async delete(sessionId: string): Promise<void> {\n this.sessions.delete(sessionId);\n }\n\n async exists(sessionId: string): Promise<boolean> {\n const stored = this.sessions.get(sessionId);\n if (!stored) return false;\n\n // Check expiration\n if (stored.session.expiresAt && stored.session.expiresAt < Date.now()) {\n this.sessions.delete(sessionId);\n return false;\n }\n return true;\n }\n\n allocId(): string {\n return randomUUID();\n }\n\n /**\n * Clean up expired sessions\n */\n cleanup(): number {\n const now = Date.now();\n let cleaned = 0;\n for (const [id, stored] of this.sessions) {\n if (stored.session.expiresAt && stored.session.expiresAt < now) {\n this.sessions.delete(id);\n cleaned++;\n }\n }\n return cleaned;\n }\n\n /**\n * Get count of active sessions\n */\n get size(): number {\n return this.sessions.size;\n }\n}\n\n/**\n * Transport Session Manager\n *\n * Manages transport sessions independent of authorization.\n * Supports both stateless (JWT-encrypted) and stateful (store-backed) modes.\n *\n * Key concepts:\n * - Authorization = User identity + permissions (1 per user token)\n * - TransportSession = Protocol-specific connection (N per authorization)\n * - One authorization can have multiple transport sessions (e.g., multiple browser tabs)\n */\nexport class TransportSessionManager {\n private readonly store: SessionStore;\n private readonly mode: 'stateless' | 'stateful';\n private readonly encryptionKey: Buffer;\n\n constructor(config: SessionStorageConfig & { encryptionSecret?: string }) {\n this.mode = config.mode;\n\n if (config.mode === 'stateless') {\n this.store = new InMemorySessionStore(); // Used only for allocation\n } else if (config.store === 'memory') {\n this.store = new InMemorySessionStore();\n } else if (config.store === 'redis') {\n // Redis store would be instantiated here\n // For now, fall back to in-memory\n // TODO: Implement RedisSessionStore\n console.warn('[TransportSessionManager] Redis store requested but not implemented - falling back to in-memory');\n this.store = new InMemorySessionStore();\n } else {\n this.store = new InMemorySessionStore();\n }\n\n // Derive encryption key from secret or generate one\n const secret = config.encryptionSecret || process.env['MCP_SESSION_SECRET'];\n if (!secret) {\n if (process.env['NODE_ENV'] === 'production') {\n throw new Error(\n '[TransportSessionManager] MCP_SESSION_SECRET or encryptionSecret is required in production. ' +\n 'Set the MCP_SESSION_SECRET environment variable or provide encryptionSecret in config.',\n );\n }\n // Development fallback - NOT secure for production\n console.warn(\n '[TransportSessionManager] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. ' +\n 'Set MCP_SESSION_SECRET or provide encryptionSecret in config.',\n );\n }\n const effectiveSecret = secret || getMachineId();\n this.encryptionKey = hkdfSha256(\n Buffer.from(effectiveSecret),\n Buffer.from('mcp-session-salt'),\n Buffer.from('transport-session'),\n 32,\n );\n }\n\n /**\n * Create a new transport session for an authorization\n *\n * @param authorizationId - The authorization this session belongs to\n * @param protocol - Transport protocol (sse, streamable-http, etc.)\n * @param options - Additional session options\n * @returns The created transport session\n */\n async createSession(\n authorizationId: string,\n protocol: TransportProtocol,\n options: {\n expiresAt?: number;\n fingerprint?: string;\n transportState?: TransportState;\n tokens?: Record<string, EncryptedBlob>;\n } = {},\n ): Promise<TransportSession> {\n const sessionId = this.store.allocId();\n\n const session: TransportSession = {\n id: sessionId,\n authorizationId,\n protocol,\n createdAt: Date.now(),\n expiresAt: options.expiresAt,\n nodeId: getMachineId(),\n clientFingerprint: options.fingerprint,\n transportState: options.transportState,\n };\n\n if (this.mode === 'stateful') {\n // Store session in persistent store\n const stored: StoredSession = {\n session,\n authorizationId,\n tokens: options.tokens,\n createdAt: Date.now(),\n lastAccessedAt: Date.now(),\n };\n await this.store.set(sessionId, stored);\n }\n\n return session;\n }\n\n /**\n * Get an existing session by ID\n *\n * @param sessionId - The session ID (encrypted JWT or UUID)\n * @returns The session if found and valid, null otherwise\n */\n async getSession(sessionId: string): Promise<TransportSession | null> {\n if (this.mode === 'stateless') {\n // Decrypt session from JWT\n return this.decryptSessionJwt(sessionId);\n }\n\n // Stateful: lookup in store\n const stored = await this.store.get(sessionId);\n return stored?.session ?? null;\n }\n\n /**\n * Get stored session with tokens (for orchestrated mode)\n */\n async getStoredSession(sessionId: string): Promise<StoredSession | null> {\n if (this.mode === 'stateless') {\n // In stateless mode, we don't have stored sessions\n return null;\n }\n return this.store.get(sessionId);\n }\n\n /**\n * Update session state\n */\n async updateSession(\n sessionId: string,\n updates: {\n transportState?: TransportState;\n expiresAt?: number;\n },\n ): Promise<boolean> {\n if (this.mode === 'stateless') {\n // Stateless sessions are immutable - caller should create new session JWT\n return false;\n }\n\n const stored = await this.store.get(sessionId);\n if (!stored) return false;\n\n if (updates.transportState) {\n stored.session.transportState = updates.transportState;\n }\n if (updates.expiresAt) {\n stored.session.expiresAt = updates.expiresAt;\n }\n stored.lastAccessedAt = Date.now();\n\n await this.store.set(sessionId, stored);\n return true;\n }\n\n /**\n * Delete a session\n */\n async deleteSession(sessionId: string): Promise<boolean> {\n if (this.mode === 'stateless') {\n // Stateless sessions can't be revoked\n return false;\n }\n\n const exists = await this.store.exists(sessionId);\n if (exists) {\n await this.store.delete(sessionId);\n }\n return exists;\n }\n\n /**\n * Encode a session as an encrypted JWT for the Mcp-Session-Id header\n *\n * @param session - The transport session to encode\n * @param additionalState - Additional encrypted state for stateless mode\n * @returns Encrypted session JWT\n */\n encodeSessionJwt(\n session: TransportSession,\n additionalState?: {\n state?: unknown;\n tokens?: Record<string, unknown>;\n },\n ): string {\n const payload: SessionJwtPayload = {\n sid: session.id,\n aid: session.authorizationId,\n proto: session.protocol,\n nid: session.nodeId,\n iat: Math.floor(Date.now() / 1000),\n exp: session.expiresAt ? Math.floor(session.expiresAt / 1000) : undefined,\n };\n\n if (this.mode === 'stateless' && additionalState) {\n const statelessPayload = payload as StatelessSessionJwtPayload;\n\n if (additionalState.state) {\n const encrypted = encryptAesGcm(this.encryptionKey, JSON.stringify(additionalState.state));\n statelessPayload.state = `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;\n }\n\n if (additionalState.tokens) {\n const encrypted = encryptAesGcm(this.encryptionKey, JSON.stringify(additionalState.tokens));\n statelessPayload.tokens = `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;\n }\n }\n\n return encryptJson(payload);\n }\n\n /**\n * Decode an encrypted session JWT\n *\n * @param jwt - The encrypted session JWT\n * @returns Decoded session or null if invalid\n */\n private decryptSessionJwt(jwt: string): TransportSession | null {\n try {\n // The encryptJson format is iv.tag.ct (base64url)\n // We need to decrypt it using the same key\n const parts = jwt.split('.');\n if (parts.length !== 3) return null;\n\n const [ivB64, tagB64, ctB64] = parts;\n const iv = Buffer.from(ivB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64');\n const tag = Buffer.from(tagB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64');\n const data = Buffer.from(ctB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64');\n\n const decrypted = decryptAesGcm(this.encryptionKey, {\n alg: 'A256GCM',\n iv: iv.toString('base64url'),\n tag: tag.toString('base64url'),\n data: data.toString('base64url'),\n });\n\n const payload = JSON.parse(decrypted) as SessionJwtPayload;\n\n // Validate expiration\n if (payload.exp && payload.exp * 1000 < Date.now()) {\n return null;\n }\n\n return {\n id: payload.sid,\n authorizationId: payload.aid,\n protocol: payload.proto,\n createdAt: payload.iat * 1000,\n expiresAt: payload.exp ? payload.exp * 1000 : undefined,\n nodeId: payload.nid,\n };\n } catch {\n return null;\n }\n }\n\n /**\n * Check if a session exists and is valid\n */\n async sessionExists(sessionId: string): Promise<boolean> {\n if (this.mode === 'stateless') {\n const session = this.decryptSessionJwt(sessionId);\n return session !== null;\n }\n return this.store.exists(sessionId);\n }\n\n /**\n * Get the storage mode\n */\n get storageMode(): 'stateless' | 'stateful' {\n return this.mode;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transport-session.manager.js","sourceRoot":"","sources":["../../../../src/auth/session/transport-session.manager.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/transport-session.manager.ts\n\nimport { randomUUID } from 'crypto';\nimport {\n TransportSession,\n TransportProtocol,\n SessionJwtPayload,\n StatelessSessionJwtPayload,\n StoredSession,\n SessionStore,\n SessionStorageConfig,\n TransportState,\n EncryptedBlob,\n} from './transport-session.types';\nimport { encryptJson } from './utils/session-id.utils';\nimport { encryptAesGcm, decryptAesGcm, hkdfSha256 } from './session.crypto';\nimport { getMachineId } from '../authorization/authorization.class';\nimport { RedisSessionStore } from './redis-session.store';\n\n/**\n * In-memory session store implementation\n */\nexport class InMemorySessionStore implements SessionStore {\n private readonly sessions = new Map<string, StoredSession>();\n\n async get(sessionId: string): Promise<StoredSession | null> {\n const stored = this.sessions.get(sessionId);\n if (!stored) return null;\n\n // Check expiration\n if (stored.session.expiresAt && stored.session.expiresAt < Date.now()) {\n this.sessions.delete(sessionId);\n return null;\n }\n\n // Update last accessed\n stored.lastAccessedAt = Date.now();\n return stored;\n }\n\n async set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void> {\n if (ttlMs) {\n session.session.expiresAt = Date.now() + ttlMs;\n }\n this.sessions.set(sessionId, session);\n }\n\n async delete(sessionId: string): Promise<void> {\n this.sessions.delete(sessionId);\n }\n\n async exists(sessionId: string): Promise<boolean> {\n const stored = this.sessions.get(sessionId);\n if (!stored) return false;\n\n // Check expiration\n if (stored.session.expiresAt && stored.session.expiresAt < Date.now()) {\n this.sessions.delete(sessionId);\n return false;\n }\n return true;\n }\n\n allocId(): string {\n return randomUUID();\n }\n\n /**\n * Clean up expired sessions\n */\n cleanup(): number {\n const now = Date.now();\n let cleaned = 0;\n for (const [id, stored] of this.sessions) {\n if (stored.session.expiresAt && stored.session.expiresAt < now) {\n this.sessions.delete(id);\n cleaned++;\n }\n }\n return cleaned;\n }\n\n /**\n * Get count of active sessions\n */\n get size(): number {\n return this.sessions.size;\n }\n}\n\n/**\n * Transport Session Manager\n *\n * Manages transport sessions independent of authorization.\n * Supports both stateless (JWT-encrypted) and stateful (store-backed) modes.\n *\n * Key concepts:\n * - Authorization = User identity + permissions (1 per user token)\n * - TransportSession = Protocol-specific connection (N per authorization)\n * - One authorization can have multiple transport sessions (e.g., multiple browser tabs)\n */\nexport class TransportSessionManager {\n private readonly store: SessionStore;\n private readonly mode: 'stateless' | 'stateful';\n private readonly encryptionKey: Buffer;\n\n constructor(config: SessionStorageConfig & { encryptionSecret?: string }) {\n this.mode = config.mode;\n\n if (config.mode === 'stateless') {\n this.store = new InMemorySessionStore(); // Used only for allocation\n } else if (config.store === 'memory') {\n this.store = new InMemorySessionStore();\n } else if (config.store === 'redis') {\n // Instantiate Redis session store\n this.store = new RedisSessionStore(config.config);\n } else {\n this.store = new InMemorySessionStore();\n }\n\n // Derive encryption key from secret or generate one\n const secret = config.encryptionSecret || process.env['MCP_SESSION_SECRET'];\n if (!secret) {\n if (process.env['NODE_ENV'] === 'production') {\n throw new Error(\n '[TransportSessionManager] MCP_SESSION_SECRET or encryptionSecret is required in production. ' +\n 'Set the MCP_SESSION_SECRET environment variable or provide encryptionSecret in config.',\n );\n }\n // Development fallback - NOT secure for production\n console.warn(\n '[TransportSessionManager] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. ' +\n 'Set MCP_SESSION_SECRET or provide encryptionSecret in config.',\n );\n }\n const effectiveSecret = secret || getMachineId();\n this.encryptionKey = hkdfSha256(\n Buffer.from(effectiveSecret),\n Buffer.from('mcp-session-salt'),\n Buffer.from('transport-session'),\n 32,\n );\n }\n\n /**\n * Create a new transport session for an authorization\n *\n * @param authorizationId - The authorization this session belongs to\n * @param protocol - Transport protocol (sse, streamable-http, etc.)\n * @param options - Additional session options\n * @returns The created transport session\n */\n async createSession(\n authorizationId: string,\n protocol: TransportProtocol,\n options: {\n expiresAt?: number;\n fingerprint?: string;\n transportState?: TransportState;\n tokens?: Record<string, EncryptedBlob>;\n } = {},\n ): Promise<TransportSession> {\n const sessionId = this.store.allocId();\n\n const session: TransportSession = {\n id: sessionId,\n authorizationId,\n protocol,\n createdAt: Date.now(),\n expiresAt: options.expiresAt,\n nodeId: getMachineId(),\n clientFingerprint: options.fingerprint,\n transportState: options.transportState,\n };\n\n if (this.mode === 'stateful') {\n // Store session in persistent store\n const stored: StoredSession = {\n session,\n authorizationId,\n tokens: options.tokens,\n createdAt: Date.now(),\n lastAccessedAt: Date.now(),\n };\n await this.store.set(sessionId, stored);\n }\n\n return session;\n }\n\n /**\n * Get an existing session by ID\n *\n * @param sessionId - The session ID (encrypted JWT or UUID)\n * @returns The session if found and valid, null otherwise\n */\n async getSession(sessionId: string): Promise<TransportSession | null> {\n if (this.mode === 'stateless') {\n // Decrypt session from JWT\n return this.decryptSessionJwt(sessionId);\n }\n\n // Stateful: lookup in store\n const stored = await this.store.get(sessionId);\n return stored?.session ?? null;\n }\n\n /**\n * Get stored session with tokens (for orchestrated mode)\n */\n async getStoredSession(sessionId: string): Promise<StoredSession | null> {\n if (this.mode === 'stateless') {\n // In stateless mode, we don't have stored sessions\n return null;\n }\n return this.store.get(sessionId);\n }\n\n /**\n * Update session state\n */\n async updateSession(\n sessionId: string,\n updates: {\n transportState?: TransportState;\n expiresAt?: number;\n },\n ): Promise<boolean> {\n if (this.mode === 'stateless') {\n // Stateless sessions are immutable - caller should create new session JWT\n return false;\n }\n\n const stored = await this.store.get(sessionId);\n if (!stored) return false;\n\n if (updates.transportState) {\n stored.session.transportState = updates.transportState;\n }\n if (updates.expiresAt) {\n stored.session.expiresAt = updates.expiresAt;\n }\n stored.lastAccessedAt = Date.now();\n\n await this.store.set(sessionId, stored);\n return true;\n }\n\n /**\n * Delete a session\n */\n async deleteSession(sessionId: string): Promise<boolean> {\n if (this.mode === 'stateless') {\n // Stateless sessions can't be revoked\n return false;\n }\n\n const exists = await this.store.exists(sessionId);\n if (exists) {\n await this.store.delete(sessionId);\n }\n return exists;\n }\n\n /**\n * Encode a session as an encrypted JWT for the Mcp-Session-Id header\n *\n * @param session - The transport session to encode\n * @param additionalState - Additional encrypted state for stateless mode\n * @returns Encrypted session JWT\n */\n encodeSessionJwt(\n session: TransportSession,\n additionalState?: {\n state?: unknown;\n tokens?: Record<string, unknown>;\n },\n ): string {\n const payload: SessionJwtPayload = {\n sid: session.id,\n aid: session.authorizationId,\n proto: session.protocol,\n nid: session.nodeId,\n iat: Math.floor(Date.now() / 1000),\n exp: session.expiresAt ? Math.floor(session.expiresAt / 1000) : undefined,\n };\n\n if (this.mode === 'stateless' && additionalState) {\n const statelessPayload = payload as StatelessSessionJwtPayload;\n\n if (additionalState.state) {\n const encrypted = encryptAesGcm(this.encryptionKey, JSON.stringify(additionalState.state));\n statelessPayload.state = `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;\n }\n\n if (additionalState.tokens) {\n const encrypted = encryptAesGcm(this.encryptionKey, JSON.stringify(additionalState.tokens));\n statelessPayload.tokens = `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;\n }\n }\n\n return encryptJson(payload);\n }\n\n /**\n * Decode an encrypted session JWT\n *\n * @param jwt - The encrypted session JWT\n * @returns Decoded session or null if invalid\n */\n private decryptSessionJwt(jwt: string): TransportSession | null {\n try {\n // The encryptJson format is iv.tag.ct (base64url)\n // We need to decrypt it using the same key\n const parts = jwt.split('.');\n if (parts.length !== 3) return null;\n\n const [ivB64, tagB64, ctB64] = parts;\n const iv = Buffer.from(ivB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64');\n const tag = Buffer.from(tagB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64');\n const data = Buffer.from(ctB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64');\n\n const decrypted = decryptAesGcm(this.encryptionKey, {\n alg: 'A256GCM',\n iv: iv.toString('base64url'),\n tag: tag.toString('base64url'),\n data: data.toString('base64url'),\n });\n\n const payload = JSON.parse(decrypted) as SessionJwtPayload;\n\n // Validate expiration\n if (payload.exp && payload.exp * 1000 < Date.now()) {\n return null;\n }\n\n return {\n id: payload.sid,\n authorizationId: payload.aid,\n protocol: payload.proto,\n createdAt: payload.iat * 1000,\n expiresAt: payload.exp ? payload.exp * 1000 : undefined,\n nodeId: payload.nid,\n };\n } catch {\n return null;\n }\n }\n\n /**\n * Check if a session exists and is valid\n */\n async sessionExists(sessionId: string): Promise<boolean> {\n if (this.mode === 'stateless') {\n const session = this.decryptSessionJwt(sessionId);\n return session !== null;\n }\n return this.store.exists(sessionId);\n }\n\n /**\n * Get the storage mode\n */\n get storageMode(): 'stateless' | 'stateful' {\n return this.mode;\n }\n}\n"]}
|
|
@@ -203,6 +203,8 @@ export interface RedisConfig {
|
|
|
203
203
|
db?: number;
|
|
204
204
|
tls?: boolean;
|
|
205
205
|
keyPrefix?: string;
|
|
206
|
+
/** Default TTL in milliseconds for session extension on access (sliding expiration) */
|
|
207
|
+
defaultTtlMs?: number;
|
|
206
208
|
}
|
|
207
209
|
export declare const transportProtocolSchema: z.ZodEnum<{
|
|
208
210
|
"legacy-sse": "legacy-sse";
|
|
@@ -437,6 +439,7 @@ export declare const redisConfigSchema: z.ZodObject<{
|
|
|
437
439
|
db: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
438
440
|
tls: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
439
441
|
keyPrefix: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
442
|
+
defaultTtlMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
440
443
|
}, z.core.$strip>;
|
|
441
444
|
export declare const sessionStorageConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
442
445
|
mode: z.ZodLiteral<"stateless">;
|
|
@@ -453,5 +456,6 @@ export declare const sessionStorageConfigSchema: z.ZodUnion<readonly [z.ZodObjec
|
|
|
453
456
|
db: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
454
457
|
tls: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
455
458
|
keyPrefix: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
459
|
+
defaultTtlMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
456
460
|
}, z.core.$strip>;
|
|
457
461
|
}, z.core.$strip>]>;
|
|
@@ -88,12 +88,13 @@ exports.storedSessionSchema = zod_1.z.object({
|
|
|
88
88
|
lastAccessedAt: zod_1.z.number(),
|
|
89
89
|
});
|
|
90
90
|
exports.redisConfigSchema = zod_1.z.object({
|
|
91
|
-
host: zod_1.z.string(),
|
|
92
|
-
port: zod_1.z.number().optional().default(6379),
|
|
91
|
+
host: zod_1.z.string().min(1),
|
|
92
|
+
port: zod_1.z.number().int().positive().optional().default(6379),
|
|
93
93
|
password: zod_1.z.string().optional(),
|
|
94
|
-
db: zod_1.z.number().optional().default(0),
|
|
94
|
+
db: zod_1.z.number().int().nonnegative().optional().default(0),
|
|
95
95
|
tls: zod_1.z.boolean().optional().default(false),
|
|
96
96
|
keyPrefix: zod_1.z.string().optional().default('mcp:session:'),
|
|
97
|
+
defaultTtlMs: zod_1.z.number().int().positive().optional().default(3600000), // 1 hour default
|
|
97
98
|
});
|
|
98
99
|
// Stateful storage options (discriminated by store type)
|
|
99
100
|
const statefulStorageSchema = zod_1.z.discriminatedUnion('store', [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport-session.types.js","sourceRoot":"","sources":["../../../../src/auth/session/transport-session.types.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/transport-session.types.ts\n\nimport { z } from 'zod';\n\n/**\n * Transport protocol types supported by MCP\n * These are the actual transport protocols for sessions (excludes 'delete-session' action)\n */\nexport type TransportProtocol = 'legacy-sse' | 'sse' | 'streamable-http' | 'stateful-http' | 'stateless-http';\n\n/**\n * Session storage mode for distributed systems\n */\nexport type SessionStorageMode = 'stateless' | 'stateful';\n\n/**\n * TransportSession represents a single client connection.\n * Multiple sessions can share the same authorization.\n * Each session is bound to a specific transport protocol.\n */\nexport interface TransportSession {\n /** Unique session ID (encrypted JWT or UUID) */\n id: string;\n\n /** Reference to the authorization this session uses */\n authorizationId: string;\n\n /** Transport protocol for this session */\n protocol: TransportProtocol;\n\n /** Session creation timestamp (epoch ms) */\n createdAt: number;\n\n /** Session expiration (epoch ms, independent of auth expiration) */\n expiresAt?: number;\n\n /** Node ID for distributed systems */\n nodeId: string;\n\n /** Client fingerprint for rate limiting/tracking */\n clientFingerprint?: string;\n\n /** Transport-specific state */\n transportState?: TransportState;\n}\n\n/**\n * Transport-specific state that varies by protocol\n */\nexport type TransportState =\n | SseTransportState\n | StreamableHttpTransportState\n | StatefulHttpTransportState\n | StatelessHttpTransportState\n | LegacySseTransportState;\n\n/**\n * SSE (Server-Sent Events) transport state\n */\nexport interface SseTransportState {\n type: 'sse';\n /** Last event ID for reconnection (per SSE spec) */\n lastEventId?: string;\n /** Connection keep-alive timestamp */\n lastPing?: number;\n /** Connection state */\n connectionState?: 'connecting' | 'open' | 'closed';\n}\n\n/**\n * Streamable HTTP transport state\n */\nexport interface StreamableHttpTransportState {\n type: 'streamable-http';\n /** Request sequence number */\n requestSeq: number;\n /** Active stream ID if streaming */\n activeStreamId?: string;\n /** Pending request IDs */\n pendingRequests?: string[];\n}\n\n/**\n * Stateful HTTP transport state\n */\nexport interface StatefulHttpTransportState {\n type: 'stateful-http';\n /** Request sequence number */\n requestSeq: number;\n /** Pending responses awaiting delivery */\n pendingResponses?: string[];\n /** Last activity timestamp */\n lastActivity?: number;\n}\n\n/**\n * Stateless HTTP transport state\n */\nexport interface StatelessHttpTransportState {\n type: 'stateless-http';\n /** Request count for rate limiting */\n requestCount: number;\n /** Window start for rate limiting */\n windowStart?: number;\n}\n\n/**\n * Legacy SSE transport state (for backwards compatibility)\n */\nexport interface LegacySseTransportState {\n type: 'legacy-sse';\n /** Message endpoint path */\n messagePath: string;\n /** Last event ID */\n lastEventId?: string;\n /** Connection state */\n connectionState?: 'connecting' | 'open' | 'closed';\n}\n\n/**\n * Session JWT payload - encodes both auth ref and transport context\n * This is the structure encrypted in the mcp-session-id header\n */\nexport interface SessionJwtPayload {\n /** Session ID (UUID) */\n sid: string;\n /** Authorization ID (token signature fingerprint) */\n aid: string;\n /** Transport protocol */\n proto: TransportProtocol;\n /** Node ID (for distributed systems) */\n nid: string;\n /** Issued at (epoch seconds) */\n iat: number;\n /** Expiration (epoch seconds) */\n exp?: number;\n}\n\n/**\n * Extended session JWT payload for stateless mode\n * Includes encrypted state and tokens\n */\nexport interface StatelessSessionJwtPayload extends SessionJwtPayload {\n /** Encrypted transport state (AES-256-GCM) */\n state?: string;\n /** Encrypted provider tokens (AES-256-GCM, for orchestrated mode) */\n tokens?: string;\n}\n\n/**\n * Stored session record (for stateful mode in Redis/memory)\n */\nexport interface StoredSession {\n /** The transport session data */\n session: TransportSession;\n /** Authorization ID reference */\n authorizationId: string;\n /** Encrypted provider tokens (for orchestrated mode) */\n tokens?: Record<string, EncryptedBlob>;\n /** Creation timestamp */\n createdAt: number;\n /** Last accessed timestamp */\n lastAccessedAt: number;\n}\n\n/**\n * Encrypted blob structure (AES-256-GCM)\n */\nexport interface EncryptedBlob {\n /** Algorithm identifier */\n alg: 'A256GCM';\n /** Key ID (for rotation) */\n kid?: string;\n /** Initialization vector (base64url) */\n iv: string;\n /** Authentication tag (base64url) */\n tag: string;\n /** Ciphertext (base64url) */\n data: string;\n /** Expiration hint (epoch seconds) */\n exp?: number;\n /** Additional metadata */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Session store interface for stateful sessions\n */\nexport interface SessionStore {\n /**\n * Get a stored session by ID\n */\n get(sessionId: string): Promise<StoredSession | null>;\n\n /**\n * Store a session with optional TTL\n */\n set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void>;\n\n /**\n * Delete a session\n */\n delete(sessionId: string): Promise<void>;\n\n /**\n * Check if a session exists\n */\n exists(sessionId: string): Promise<boolean>;\n\n /**\n * Allocate a new session ID\n */\n allocId(): string;\n}\n\n/**\n * Session storage configuration\n */\nexport type SessionStorageConfig =\n | { mode: 'stateless' }\n | { mode: 'stateful'; store: 'memory' }\n | { mode: 'stateful'; store: 'redis'; config: RedisConfig };\n\n/**\n * Redis configuration\n */\nexport interface RedisConfig {\n host: string;\n port?: number;\n password?: string;\n db?: number;\n tls?: boolean;\n keyPrefix?: string;\n}\n\n// ============================================\n// Zod Schemas\n// ============================================\n\nexport const transportProtocolSchema = z.enum([\n 'legacy-sse',\n 'sse',\n 'streamable-http',\n 'stateful-http',\n 'stateless-http',\n]);\n\nexport const sseTransportStateSchema = z.object({\n type: z.literal('sse'),\n lastEventId: z.string().optional(),\n lastPing: z.number().optional(),\n connectionState: z.enum(['connecting', 'open', 'closed']).optional(),\n});\n\nexport const streamableHttpTransportStateSchema = z.object({\n type: z.literal('streamable-http'),\n requestSeq: z.number(),\n activeStreamId: z.string().optional(),\n pendingRequests: z.array(z.string()).optional(),\n});\n\nexport const statefulHttpTransportStateSchema = z.object({\n type: z.literal('stateful-http'),\n requestSeq: z.number(),\n pendingResponses: z.array(z.string()).optional(),\n lastActivity: z.number().optional(),\n});\n\nexport const statelessHttpTransportStateSchema = z.object({\n type: z.literal('stateless-http'),\n requestCount: z.number(),\n windowStart: z.number().optional(),\n});\n\nexport const legacySseTransportStateSchema = z.object({\n type: z.literal('legacy-sse'),\n messagePath: z.string(),\n lastEventId: z.string().optional(),\n connectionState: z.enum(['connecting', 'open', 'closed']).optional(),\n});\n\nexport const transportStateSchema = z.discriminatedUnion('type', [\n sseTransportStateSchema,\n streamableHttpTransportStateSchema,\n statefulHttpTransportStateSchema,\n statelessHttpTransportStateSchema,\n legacySseTransportStateSchema,\n]);\n\nexport const transportSessionSchema = z.object({\n id: z.string(),\n authorizationId: z.string(),\n protocol: transportProtocolSchema,\n createdAt: z.number(),\n expiresAt: z.number().optional(),\n nodeId: z.string(),\n clientFingerprint: z.string().optional(),\n transportState: transportStateSchema.optional(),\n});\n\nexport const sessionJwtPayloadSchema = z.object({\n sid: z.string(),\n aid: z.string(),\n proto: transportProtocolSchema,\n nid: z.string(),\n iat: z.number(),\n exp: z.number().optional(),\n});\n\nexport const statelessSessionJwtPayloadSchema = sessionJwtPayloadSchema.extend({\n state: z.string().optional(),\n tokens: z.string().optional(),\n});\n\nexport const encryptedBlobSchema = z.object({\n alg: z.literal('A256GCM'),\n kid: z.string().optional(),\n iv: z.string(),\n tag: z.string(),\n data: z.string(),\n exp: z.number().optional(),\n meta: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const storedSessionSchema = z.object({\n session: transportSessionSchema,\n authorizationId: z.string(),\n tokens: z.record(z.string(), encryptedBlobSchema).optional(),\n createdAt: z.number(),\n lastAccessedAt: z.number(),\n});\n\nexport const redisConfigSchema = z.object({\n host: z.string(),\n port: z.number().optional().default(6379),\n password: z.string().optional(),\n db: z.number().optional().default(0),\n tls: z.boolean().optional().default(false),\n keyPrefix: z.string().optional().default('mcp:session:'),\n});\n\n// Stateful storage options (discriminated by store type)\nconst statefulStorageSchema = z.discriminatedUnion('store', [\n z.object({ store: z.literal('memory') }),\n z.object({ store: z.literal('redis'), config: redisConfigSchema }),\n]);\n\n// Session storage config using union instead of discriminatedUnion\n// to avoid duplicate mode values\nexport const sessionStorageConfigSchema = z.union([\n z.object({ mode: z.literal('stateless') }),\n z.object({ mode: z.literal('stateful') }).merge(statefulStorageSchema.options[0]),\n z.object({ mode: z.literal('stateful') }).merge(statefulStorageSchema.options[1]),\n]);\n"]}
|
|
1
|
+
{"version":3,"file":"transport-session.types.js","sourceRoot":"","sources":["../../../../src/auth/session/transport-session.types.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/transport-session.types.ts\n\nimport { z } from 'zod';\n\n/**\n * Transport protocol types supported by MCP\n * These are the actual transport protocols for sessions (excludes 'delete-session' action)\n */\nexport type TransportProtocol = 'legacy-sse' | 'sse' | 'streamable-http' | 'stateful-http' | 'stateless-http';\n\n/**\n * Session storage mode for distributed systems\n */\nexport type SessionStorageMode = 'stateless' | 'stateful';\n\n/**\n * TransportSession represents a single client connection.\n * Multiple sessions can share the same authorization.\n * Each session is bound to a specific transport protocol.\n */\nexport interface TransportSession {\n /** Unique session ID (encrypted JWT or UUID) */\n id: string;\n\n /** Reference to the authorization this session uses */\n authorizationId: string;\n\n /** Transport protocol for this session */\n protocol: TransportProtocol;\n\n /** Session creation timestamp (epoch ms) */\n createdAt: number;\n\n /** Session expiration (epoch ms, independent of auth expiration) */\n expiresAt?: number;\n\n /** Node ID for distributed systems */\n nodeId: string;\n\n /** Client fingerprint for rate limiting/tracking */\n clientFingerprint?: string;\n\n /** Transport-specific state */\n transportState?: TransportState;\n}\n\n/**\n * Transport-specific state that varies by protocol\n */\nexport type TransportState =\n | SseTransportState\n | StreamableHttpTransportState\n | StatefulHttpTransportState\n | StatelessHttpTransportState\n | LegacySseTransportState;\n\n/**\n * SSE (Server-Sent Events) transport state\n */\nexport interface SseTransportState {\n type: 'sse';\n /** Last event ID for reconnection (per SSE spec) */\n lastEventId?: string;\n /** Connection keep-alive timestamp */\n lastPing?: number;\n /** Connection state */\n connectionState?: 'connecting' | 'open' | 'closed';\n}\n\n/**\n * Streamable HTTP transport state\n */\nexport interface StreamableHttpTransportState {\n type: 'streamable-http';\n /** Request sequence number */\n requestSeq: number;\n /** Active stream ID if streaming */\n activeStreamId?: string;\n /** Pending request IDs */\n pendingRequests?: string[];\n}\n\n/**\n * Stateful HTTP transport state\n */\nexport interface StatefulHttpTransportState {\n type: 'stateful-http';\n /** Request sequence number */\n requestSeq: number;\n /** Pending responses awaiting delivery */\n pendingResponses?: string[];\n /** Last activity timestamp */\n lastActivity?: number;\n}\n\n/**\n * Stateless HTTP transport state\n */\nexport interface StatelessHttpTransportState {\n type: 'stateless-http';\n /** Request count for rate limiting */\n requestCount: number;\n /** Window start for rate limiting */\n windowStart?: number;\n}\n\n/**\n * Legacy SSE transport state (for backwards compatibility)\n */\nexport interface LegacySseTransportState {\n type: 'legacy-sse';\n /** Message endpoint path */\n messagePath: string;\n /** Last event ID */\n lastEventId?: string;\n /** Connection state */\n connectionState?: 'connecting' | 'open' | 'closed';\n}\n\n/**\n * Session JWT payload - encodes both auth ref and transport context\n * This is the structure encrypted in the mcp-session-id header\n */\nexport interface SessionJwtPayload {\n /** Session ID (UUID) */\n sid: string;\n /** Authorization ID (token signature fingerprint) */\n aid: string;\n /** Transport protocol */\n proto: TransportProtocol;\n /** Node ID (for distributed systems) */\n nid: string;\n /** Issued at (epoch seconds) */\n iat: number;\n /** Expiration (epoch seconds) */\n exp?: number;\n}\n\n/**\n * Extended session JWT payload for stateless mode\n * Includes encrypted state and tokens\n */\nexport interface StatelessSessionJwtPayload extends SessionJwtPayload {\n /** Encrypted transport state (AES-256-GCM) */\n state?: string;\n /** Encrypted provider tokens (AES-256-GCM, for orchestrated mode) */\n tokens?: string;\n}\n\n/**\n * Stored session record (for stateful mode in Redis/memory)\n */\nexport interface StoredSession {\n /** The transport session data */\n session: TransportSession;\n /** Authorization ID reference */\n authorizationId: string;\n /** Encrypted provider tokens (for orchestrated mode) */\n tokens?: Record<string, EncryptedBlob>;\n /** Creation timestamp */\n createdAt: number;\n /** Last accessed timestamp */\n lastAccessedAt: number;\n}\n\n/**\n * Encrypted blob structure (AES-256-GCM)\n */\nexport interface EncryptedBlob {\n /** Algorithm identifier */\n alg: 'A256GCM';\n /** Key ID (for rotation) */\n kid?: string;\n /** Initialization vector (base64url) */\n iv: string;\n /** Authentication tag (base64url) */\n tag: string;\n /** Ciphertext (base64url) */\n data: string;\n /** Expiration hint (epoch seconds) */\n exp?: number;\n /** Additional metadata */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Session store interface for stateful sessions\n */\nexport interface SessionStore {\n /**\n * Get a stored session by ID\n */\n get(sessionId: string): Promise<StoredSession | null>;\n\n /**\n * Store a session with optional TTL\n */\n set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void>;\n\n /**\n * Delete a session\n */\n delete(sessionId: string): Promise<void>;\n\n /**\n * Check if a session exists\n */\n exists(sessionId: string): Promise<boolean>;\n\n /**\n * Allocate a new session ID\n */\n allocId(): string;\n}\n\n/**\n * Session storage configuration\n */\nexport type SessionStorageConfig =\n | { mode: 'stateless' }\n | { mode: 'stateful'; store: 'memory' }\n | { mode: 'stateful'; store: 'redis'; config: RedisConfig };\n\n/**\n * Redis configuration\n */\nexport interface RedisConfig {\n host: string;\n port?: number;\n password?: string;\n db?: number;\n tls?: boolean;\n keyPrefix?: string;\n /** Default TTL in milliseconds for session extension on access (sliding expiration) */\n defaultTtlMs?: number;\n}\n\n// ============================================\n// Zod Schemas\n// ============================================\n\nexport const transportProtocolSchema = z.enum([\n 'legacy-sse',\n 'sse',\n 'streamable-http',\n 'stateful-http',\n 'stateless-http',\n]);\n\nexport const sseTransportStateSchema = z.object({\n type: z.literal('sse'),\n lastEventId: z.string().optional(),\n lastPing: z.number().optional(),\n connectionState: z.enum(['connecting', 'open', 'closed']).optional(),\n});\n\nexport const streamableHttpTransportStateSchema = z.object({\n type: z.literal('streamable-http'),\n requestSeq: z.number(),\n activeStreamId: z.string().optional(),\n pendingRequests: z.array(z.string()).optional(),\n});\n\nexport const statefulHttpTransportStateSchema = z.object({\n type: z.literal('stateful-http'),\n requestSeq: z.number(),\n pendingResponses: z.array(z.string()).optional(),\n lastActivity: z.number().optional(),\n});\n\nexport const statelessHttpTransportStateSchema = z.object({\n type: z.literal('stateless-http'),\n requestCount: z.number(),\n windowStart: z.number().optional(),\n});\n\nexport const legacySseTransportStateSchema = z.object({\n type: z.literal('legacy-sse'),\n messagePath: z.string(),\n lastEventId: z.string().optional(),\n connectionState: z.enum(['connecting', 'open', 'closed']).optional(),\n});\n\nexport const transportStateSchema = z.discriminatedUnion('type', [\n sseTransportStateSchema,\n streamableHttpTransportStateSchema,\n statefulHttpTransportStateSchema,\n statelessHttpTransportStateSchema,\n legacySseTransportStateSchema,\n]);\n\nexport const transportSessionSchema = z.object({\n id: z.string(),\n authorizationId: z.string(),\n protocol: transportProtocolSchema,\n createdAt: z.number(),\n expiresAt: z.number().optional(),\n nodeId: z.string(),\n clientFingerprint: z.string().optional(),\n transportState: transportStateSchema.optional(),\n});\n\nexport const sessionJwtPayloadSchema = z.object({\n sid: z.string(),\n aid: z.string(),\n proto: transportProtocolSchema,\n nid: z.string(),\n iat: z.number(),\n exp: z.number().optional(),\n});\n\nexport const statelessSessionJwtPayloadSchema = sessionJwtPayloadSchema.extend({\n state: z.string().optional(),\n tokens: z.string().optional(),\n});\n\nexport const encryptedBlobSchema = z.object({\n alg: z.literal('A256GCM'),\n kid: z.string().optional(),\n iv: z.string(),\n tag: z.string(),\n data: z.string(),\n exp: z.number().optional(),\n meta: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const storedSessionSchema = z.object({\n session: transportSessionSchema,\n authorizationId: z.string(),\n tokens: z.record(z.string(), encryptedBlobSchema).optional(),\n createdAt: z.number(),\n lastAccessedAt: z.number(),\n});\n\nexport const redisConfigSchema = z.object({\n host: z.string().min(1),\n port: z.number().int().positive().optional().default(6379),\n password: z.string().optional(),\n db: z.number().int().nonnegative().optional().default(0),\n tls: z.boolean().optional().default(false),\n keyPrefix: z.string().optional().default('mcp:session:'),\n defaultTtlMs: z.number().int().positive().optional().default(3600000), // 1 hour default\n});\n\n// Stateful storage options (discriminated by store type)\nconst statefulStorageSchema = z.discriminatedUnion('store', [\n z.object({ store: z.literal('memory') }),\n z.object({ store: z.literal('redis'), config: redisConfigSchema }),\n]);\n\n// Session storage config using union instead of discriminatedUnion\n// to avoid duplicate mode values\nexport const sessionStorageConfigSchema = z.union([\n z.object({ mode: z.literal('stateless') }),\n z.object({ mode: z.literal('stateful') }).merge(statefulStorageSchema.options[0]),\n z.object({ mode: z.literal('stateful') }).merge(statefulStorageSchema.options[1]),\n]);\n"]}
|
|
@@ -3,7 +3,8 @@ import type { PlatformDetectionConfig } from '../../../common/types/options/sess
|
|
|
3
3
|
export declare function encryptJson(obj: unknown): string;
|
|
4
4
|
/**
|
|
5
5
|
* Decrypt a public session ID without signature verification.
|
|
6
|
-
* Public sessions use authSig: 'public' and isPublic: true
|
|
6
|
+
* Public sessions use authSig: 'public' and isPublic: true.
|
|
7
|
+
* First checks the cache for potentially updated payload (e.g., platformType).
|
|
7
8
|
*/
|
|
8
9
|
export declare function decryptPublicSession(sessionId: string): SessionIdPayload | null;
|
|
9
10
|
/**
|
|
@@ -27,3 +28,13 @@ export declare function createSessionId(protocol: TransportProtocolType, token:
|
|
|
27
28
|
};
|
|
28
29
|
export declare function generateSessionCookie(sessionId: string, ttlInMinutes?: number): string;
|
|
29
30
|
export declare function extractSessionFromCookie(cookie?: string): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Update a cached session payload with new data.
|
|
33
|
+
* This is used to persist changes like platformType detection that happen
|
|
34
|
+
* after the initial session creation.
|
|
35
|
+
*
|
|
36
|
+
* @param sessionId - The session ID to update
|
|
37
|
+
* @param updates - Partial payload updates to merge
|
|
38
|
+
* @returns true if the session was found and updated, false otherwise
|
|
39
|
+
*/
|
|
40
|
+
export declare function updateSessionPayload(sessionId: string, updates: Partial<SessionIdPayload>): boolean;
|
|
@@ -6,21 +6,19 @@ exports.parseSessionHeader = parseSessionHeader;
|
|
|
6
6
|
exports.createSessionId = createSessionId;
|
|
7
7
|
exports.generateSessionCookie = generateSessionCookie;
|
|
8
8
|
exports.extractSessionFromCookie = extractSessionFromCookie;
|
|
9
|
+
exports.updateSessionPayload = updateSessionPayload;
|
|
9
10
|
// auth/session/utils/session-id.utils.ts
|
|
10
11
|
const crypto_1 = require("crypto");
|
|
11
12
|
const tiny_ttl_cache_1 = require("./tiny-ttl-cache");
|
|
12
13
|
const auth_token_utils_1 = require("./auth-token.utils");
|
|
13
14
|
const notification_service_1 = require("../../../notification/notification.service");
|
|
15
|
+
const machine_id_1 = require("../../machine-id");
|
|
14
16
|
// 5s TTL cache for decrypted headers
|
|
15
17
|
const cache = new tiny_ttl_cache_1.TinyTtlCache(5000);
|
|
16
|
-
// Single-process machine id generated at server launch
|
|
17
|
-
const MACHINE_ID = (() => {
|
|
18
|
-
// Prefer an injected env (stable across restarts) if you have one; else random per launch:
|
|
19
|
-
return process.env['MACHINE_ID'] || (0, crypto_1.randomUUID)(); // TODO: move to gateway config module
|
|
20
|
-
})();
|
|
21
18
|
// Symmetric key derived from secret or machine id (stable for the process)
|
|
19
|
+
// Uses getMachineId() from authorization module as single source of truth
|
|
22
20
|
function getKey() {
|
|
23
|
-
const base = process.env['MCP_SESSION_SECRET'] ||
|
|
21
|
+
const base = process.env['MCP_SESSION_SECRET'] || (0, machine_id_1.getMachineId)();
|
|
24
22
|
return (0, crypto_1.createHash)('sha256').update(base).digest(); // 32 bytes
|
|
25
23
|
}
|
|
26
24
|
function b64urlEncode(buf) {
|
|
@@ -87,11 +85,23 @@ function decryptSessionId(sessionId, sig) {
|
|
|
87
85
|
}
|
|
88
86
|
/**
|
|
89
87
|
* Decrypt a public session ID without signature verification.
|
|
90
|
-
* Public sessions use authSig: 'public' and isPublic: true
|
|
88
|
+
* Public sessions use authSig: 'public' and isPublic: true.
|
|
89
|
+
* First checks the cache for potentially updated payload (e.g., platformType).
|
|
91
90
|
*/
|
|
92
91
|
function decryptPublicSession(sessionId) {
|
|
92
|
+
// Check cache first - may have updated fields like platformType
|
|
93
|
+
const cached = cache.get(sessionId);
|
|
94
|
+
if (cached && isValidPublicSessionPayload(cached)) {
|
|
95
|
+
return cached;
|
|
96
|
+
}
|
|
97
|
+
// Fall back to decrypting from the encrypted session ID
|
|
93
98
|
const dec = safeDecrypt(sessionId);
|
|
94
|
-
|
|
99
|
+
if (isValidPublicSessionPayload(dec)) {
|
|
100
|
+
// Cache the decrypted payload for future requests
|
|
101
|
+
cache.set(sessionId, dec);
|
|
102
|
+
return dec;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
95
105
|
}
|
|
96
106
|
/**
|
|
97
107
|
* Safe wrapper around decryptSessionJson that catches crypto/parse errors.
|
|
@@ -154,7 +164,7 @@ function createSessionId(protocol, token, options) {
|
|
|
154
164
|
}
|
|
155
165
|
}
|
|
156
166
|
const payload = {
|
|
157
|
-
nodeId:
|
|
167
|
+
nodeId: (0, machine_id_1.getMachineId)(),
|
|
158
168
|
authSig,
|
|
159
169
|
uuid: (0, crypto_1.randomUUID)(),
|
|
160
170
|
iat: nowSec(),
|
|
@@ -175,4 +185,33 @@ function extractSessionFromCookie(cookie) {
|
|
|
175
185
|
const m = cookie.match(/(^|;)\s*mcp_session_id\s*=\s*([^;]*)/);
|
|
176
186
|
return m ? m[2] : undefined;
|
|
177
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Update a cached session payload with new data.
|
|
190
|
+
* This is used to persist changes like platformType detection that happen
|
|
191
|
+
* after the initial session creation.
|
|
192
|
+
*
|
|
193
|
+
* @param sessionId - The session ID to update
|
|
194
|
+
* @param updates - Partial payload updates to merge
|
|
195
|
+
* @returns true if the session was found and updated, false otherwise
|
|
196
|
+
*/
|
|
197
|
+
function updateSessionPayload(sessionId, updates) {
|
|
198
|
+
const existing = cache.get(sessionId);
|
|
199
|
+
if (existing) {
|
|
200
|
+
// Merge updates into existing payload
|
|
201
|
+
Object.assign(existing, updates);
|
|
202
|
+
// Re-set to refresh TTL
|
|
203
|
+
cache.set(sessionId, existing);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
// Try to decrypt and update if not in cache
|
|
207
|
+
const decrypted = safeDecrypt(sessionId);
|
|
208
|
+
if (isValidSessionPayload(decrypted, decrypted?.authSig || '') ||
|
|
209
|
+
isValidPublicSessionPayload(decrypted)) {
|
|
210
|
+
const payload = decrypted;
|
|
211
|
+
Object.assign(payload, updates);
|
|
212
|
+
cache.set(sessionId, payload);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
178
217
|
//# sourceMappingURL=session-id.utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-id.utils.js","sourceRoot":"","sources":["../../../../../src/auth/session/utils/session-id.utils.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/utils/session-id.utils.ts\nimport { randomUUID, createHash, randomBytes, createCipheriv, createDecipheriv } from 'crypto';\nimport { TinyTtlCache } from './tiny-ttl-cache';\nimport { SessionIdPayload, TransportProtocolType, AIPlatformType } from '../../../common';\nimport { getTokenSignatureFingerprint } from './auth-token.utils';\nimport { detectPlatformFromUserAgent } from '../../../notification/notification.service';\nimport type { PlatformDetectionConfig } from '../../../common/types/options/session.options';\n\n// 5s TTL cache for decrypted headers\nconst cache = new TinyTtlCache<string, SessionIdPayload>(5000);\n\n// Single-process machine id generated at server launch\nconst MACHINE_ID = (() => {\n // Prefer an injected env (stable across restarts) if you have one; else random per launch:\n return process.env['MACHINE_ID'] || randomUUID(); // TODO: move to gateway config module\n})();\n\n// Symmetric key derived from secret or machine id (stable for the process)\nfunction getKey(): Buffer {\n const base = process.env['MCP_SESSION_SECRET'] || MACHINE_ID; // TODO: move to gateway config module\n return createHash('sha256').update(base).digest(); // 32 bytes\n}\n\nfunction b64urlEncode(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction b64urlDecode(s: string): Buffer {\n const pad = 4 - (s.length % 4);\n const base64 = s.replace(/-/g, '+').replace(/_/g, '/') + (pad < 4 ? '='.repeat(pad) : '');\n return Buffer.from(base64, 'base64');\n}\n\nexport function encryptJson(obj: unknown): string {\n const key = getKey();\n const iv = randomBytes(12); // AES-GCM 96-bit IV\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const pt = Buffer.from(JSON.stringify(obj), 'utf8');\n const ct = Buffer.concat([cipher.update(pt), cipher.final()]);\n const tag = cipher.getAuthTag();\n // Pack iv.tag.ct as base64url(iv.tag.ct)\n return `${b64urlEncode(iv)}.${b64urlEncode(tag)}.${b64urlEncode(ct)}`;\n}\n\n/**\n * Low-level decryption that returns the raw JSON payload or null.\n * Handles all crypto/parsing failures by returning null.\n */\nfunction decryptSessionJson(sessionId: string): unknown {\n const parts = sessionId.split('.');\n if (parts.length !== 3) return null;\n\n const [ivB64, tagB64, ctB64] = parts;\n if (!ivB64 || !tagB64 || !ctB64) return null;\n\n const key = getKey();\n const iv = b64urlDecode(ivB64);\n const tag = b64urlDecode(tagB64);\n const ct = b64urlDecode(ctB64);\n\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n const pt = Buffer.concat([decipher.update(ct), decipher.final()]);\n return JSON.parse(pt.toString('utf8'));\n}\n\nfunction isValidSessionPayload(dec: unknown, sig: string): dec is SessionIdPayload {\n if (typeof dec !== 'object' || dec === null) return false;\n const d = dec as Record<string, unknown>;\n return (\n typeof d['nodeId'] === 'string' &&\n typeof d['authSig'] === 'string' &&\n typeof d['uuid'] === 'string' &&\n typeof d['iat'] === 'number' &&\n d['authSig'] === sig\n );\n}\n\nfunction isValidPublicSessionPayload(dec: unknown): dec is SessionIdPayload {\n if (typeof dec !== 'object' || dec === null) return false;\n const d = dec as Record<string, unknown>;\n return (\n typeof d['nodeId'] === 'string' &&\n d['authSig'] === 'public' &&\n typeof d['uuid'] === 'string' &&\n typeof d['iat'] === 'number' &&\n d['isPublic'] === true\n );\n}\n\nfunction decryptSessionId(sessionId: string, sig: string): SessionIdPayload | null {\n const dec = safeDecrypt(sessionId);\n return isValidSessionPayload(dec, sig) ? dec : null;\n}\n\n/**\n * Decrypt a public session ID without signature verification.\n * Public sessions use authSig: 'public' and isPublic: true\n */\nexport function decryptPublicSession(sessionId: string): SessionIdPayload | null {\n const dec = safeDecrypt(sessionId);\n return isValidPublicSessionPayload(dec) ? dec : null;\n}\n\n/**\n * Safe wrapper around decryptSessionJson that catches crypto/parse errors.\n */\nfunction safeDecrypt(sessionId: string): unknown {\n try {\n return decryptSessionJson(sessionId);\n } catch {\n return null;\n }\n}\n\nfunction nowSec(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * Validates an existing session header OR creates a fresh one.\n * - Valid: nodeId matches local, authSig matches current Authorization\n * - On any mismatch/decrypt error → generate new\n */\nexport function parseSessionHeader(\n sessionHeader: string | undefined,\n token: string,\n): { id: string; payload: SessionIdPayload } | undefined {\n const currentAuthSig = getTokenSignatureFingerprint(token);\n if (sessionHeader) {\n const cached = cache.get(sessionHeader);\n if (cached) {\n if (cached.authSig === currentAuthSig) {\n return { id: sessionHeader, payload: cached };\n }\n // fallthrough to regenerate if mismatch\n }\n\n const dec = decryptSessionId(sessionHeader, currentAuthSig);\n if (dec) {\n cache.set(sessionHeader, dec);\n return { id: sessionHeader, payload: dec as SessionIdPayload };\n }\n }\n\n return undefined;\n // // Create fresh\n\n // const decodedSse: SessionIdPayload = {\n // nodeId: MACHINE_ID,\n // authSig: currentAuthSig,\n // uuid: randomUUID(),\n // iat: nowSec(),\n // };\n // const header = encryptJson(decoded);\n // const headerSse = encryptJson(decodedSse);\n // cache.set(header, decoded);\n // cache.set(headerSse, decodedSse);\n // return { header, decoded, headerSse, isNew: true };\n}\n\nexport interface CreateSessionOptions {\n /** User-Agent header for pre-initialize platform detection */\n userAgent?: string;\n /** Platform detection configuration from scope */\n platformDetectionConfig?: PlatformDetectionConfig;\n}\n\nexport function createSessionId(protocol: TransportProtocolType, token: string, options?: CreateSessionOptions) {\n const authSig = getTokenSignatureFingerprint(token);\n\n // Detect platform from user-agent if provided (before MCP initialize)\n let platformType: AIPlatformType | undefined;\n if (options?.userAgent) {\n platformType = detectPlatformFromUserAgent(options.userAgent, options.platformDetectionConfig);\n // Only set if we detected something meaningful\n if (platformType === 'unknown') {\n platformType = undefined;\n }\n }\n\n const payload: SessionIdPayload = {\n nodeId: MACHINE_ID,\n authSig,\n uuid: randomUUID(),\n iat: nowSec(),\n protocol,\n platformType,\n };\n const id = encryptJson(payload);\n cache.set(id, payload);\n return { id, payload };\n}\n\nexport function generateSessionCookie(sessionId: string, ttlInMinutes = 60 * 24): string {\n const expires = new Date(Date.now() + ttlInMinutes * 60 * 1000).toUTCString();\n return `mcp_session_id=${sessionId}; Path=/; Expires=${expires}; HttpOnly; SameSite=Lax`;\n}\n\nexport function extractSessionFromCookie(cookie?: string): string | undefined {\n if (!cookie) return undefined;\n const m = cookie.match(/(^|;)\\s*mcp_session_id\\s*=\\s*([^;]*)/);\n return m ? m[2] : undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-id.utils.js","sourceRoot":"","sources":["../../../../../src/auth/session/utils/session-id.utils.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/utils/session-id.utils.ts\nimport { randomUUID, createHash, randomBytes, createCipheriv, createDecipheriv } from 'crypto';\nimport { TinyTtlCache } from './tiny-ttl-cache';\nimport { SessionIdPayload, TransportProtocolType, AIPlatformType } from '../../../common';\nimport { getTokenSignatureFingerprint } from './auth-token.utils';\nimport { detectPlatformFromUserAgent } from '../../../notification/notification.service';\nimport type { PlatformDetectionConfig } from '../../../common/types/options/session.options';\nimport { getMachineId } from '../../machine-id';\n\n// 5s TTL cache for decrypted headers\nconst cache = new TinyTtlCache<string, SessionIdPayload>(5000);\n\n// Symmetric key derived from secret or machine id (stable for the process)\n// Uses getMachineId() from authorization module as single source of truth\nfunction getKey(): Buffer {\n const base = process.env['MCP_SESSION_SECRET'] || getMachineId();\n return createHash('sha256').update(base).digest(); // 32 bytes\n}\n\nfunction b64urlEncode(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction b64urlDecode(s: string): Buffer {\n const pad = 4 - (s.length % 4);\n const base64 = s.replace(/-/g, '+').replace(/_/g, '/') + (pad < 4 ? '='.repeat(pad) : '');\n return Buffer.from(base64, 'base64');\n}\n\nexport function encryptJson(obj: unknown): string {\n const key = getKey();\n const iv = randomBytes(12); // AES-GCM 96-bit IV\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const pt = Buffer.from(JSON.stringify(obj), 'utf8');\n const ct = Buffer.concat([cipher.update(pt), cipher.final()]);\n const tag = cipher.getAuthTag();\n // Pack iv.tag.ct as base64url(iv.tag.ct)\n return `${b64urlEncode(iv)}.${b64urlEncode(tag)}.${b64urlEncode(ct)}`;\n}\n\n/**\n * Low-level decryption that returns the raw JSON payload or null.\n * Handles all crypto/parsing failures by returning null.\n */\nfunction decryptSessionJson(sessionId: string): unknown {\n const parts = sessionId.split('.');\n if (parts.length !== 3) return null;\n\n const [ivB64, tagB64, ctB64] = parts;\n if (!ivB64 || !tagB64 || !ctB64) return null;\n\n const key = getKey();\n const iv = b64urlDecode(ivB64);\n const tag = b64urlDecode(tagB64);\n const ct = b64urlDecode(ctB64);\n\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n const pt = Buffer.concat([decipher.update(ct), decipher.final()]);\n return JSON.parse(pt.toString('utf8'));\n}\n\nfunction isValidSessionPayload(dec: unknown, sig: string): dec is SessionIdPayload {\n if (typeof dec !== 'object' || dec === null) return false;\n const d = dec as Record<string, unknown>;\n return (\n typeof d['nodeId'] === 'string' &&\n typeof d['authSig'] === 'string' &&\n typeof d['uuid'] === 'string' &&\n typeof d['iat'] === 'number' &&\n d['authSig'] === sig\n );\n}\n\nfunction isValidPublicSessionPayload(dec: unknown): dec is SessionIdPayload {\n if (typeof dec !== 'object' || dec === null) return false;\n const d = dec as Record<string, unknown>;\n return (\n typeof d['nodeId'] === 'string' &&\n d['authSig'] === 'public' &&\n typeof d['uuid'] === 'string' &&\n typeof d['iat'] === 'number' &&\n d['isPublic'] === true\n );\n}\n\nfunction decryptSessionId(sessionId: string, sig: string): SessionIdPayload | null {\n const dec = safeDecrypt(sessionId);\n return isValidSessionPayload(dec, sig) ? dec : null;\n}\n\n/**\n * Decrypt a public session ID without signature verification.\n * Public sessions use authSig: 'public' and isPublic: true.\n * First checks the cache for potentially updated payload (e.g., platformType).\n */\nexport function decryptPublicSession(sessionId: string): SessionIdPayload | null {\n // Check cache first - may have updated fields like platformType\n const cached = cache.get(sessionId);\n if (cached && isValidPublicSessionPayload(cached)) {\n return cached;\n }\n\n // Fall back to decrypting from the encrypted session ID\n const dec = safeDecrypt(sessionId);\n if (isValidPublicSessionPayload(dec)) {\n // Cache the decrypted payload for future requests\n cache.set(sessionId, dec as SessionIdPayload);\n return dec as SessionIdPayload;\n }\n return null;\n}\n\n/**\n * Safe wrapper around decryptSessionJson that catches crypto/parse errors.\n */\nfunction safeDecrypt(sessionId: string): unknown {\n try {\n return decryptSessionJson(sessionId);\n } catch {\n return null;\n }\n}\n\nfunction nowSec(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * Validates an existing session header OR creates a fresh one.\n * - Valid: nodeId matches local, authSig matches current Authorization\n * - On any mismatch/decrypt error → generate new\n */\nexport function parseSessionHeader(\n sessionHeader: string | undefined,\n token: string,\n): { id: string; payload: SessionIdPayload } | undefined {\n const currentAuthSig = getTokenSignatureFingerprint(token);\n if (sessionHeader) {\n const cached = cache.get(sessionHeader);\n if (cached) {\n if (cached.authSig === currentAuthSig) {\n return { id: sessionHeader, payload: cached };\n }\n // fallthrough to regenerate if mismatch\n }\n\n const dec = decryptSessionId(sessionHeader, currentAuthSig);\n if (dec) {\n cache.set(sessionHeader, dec);\n return { id: sessionHeader, payload: dec as SessionIdPayload };\n }\n }\n\n return undefined;\n // // Create fresh\n\n // const decodedSse: SessionIdPayload = {\n // nodeId: MACHINE_ID,\n // authSig: currentAuthSig,\n // uuid: randomUUID(),\n // iat: nowSec(),\n // };\n // const header = encryptJson(decoded);\n // const headerSse = encryptJson(decodedSse);\n // cache.set(header, decoded);\n // cache.set(headerSse, decodedSse);\n // return { header, decoded, headerSse, isNew: true };\n}\n\nexport interface CreateSessionOptions {\n /** User-Agent header for pre-initialize platform detection */\n userAgent?: string;\n /** Platform detection configuration from scope */\n platformDetectionConfig?: PlatformDetectionConfig;\n}\n\nexport function createSessionId(protocol: TransportProtocolType, token: string, options?: CreateSessionOptions) {\n const authSig = getTokenSignatureFingerprint(token);\n\n // Detect platform from user-agent if provided (before MCP initialize)\n let platformType: AIPlatformType | undefined;\n if (options?.userAgent) {\n platformType = detectPlatformFromUserAgent(options.userAgent, options.platformDetectionConfig);\n // Only set if we detected something meaningful\n if (platformType === 'unknown') {\n platformType = undefined;\n }\n }\n\n const payload: SessionIdPayload = {\n nodeId: getMachineId(),\n authSig,\n uuid: randomUUID(),\n iat: nowSec(),\n protocol,\n platformType,\n };\n const id = encryptJson(payload);\n cache.set(id, payload);\n return { id, payload };\n}\n\nexport function generateSessionCookie(sessionId: string, ttlInMinutes = 60 * 24): string {\n const expires = new Date(Date.now() + ttlInMinutes * 60 * 1000).toUTCString();\n return `mcp_session_id=${sessionId}; Path=/; Expires=${expires}; HttpOnly; SameSite=Lax`;\n}\n\nexport function extractSessionFromCookie(cookie?: string): string | undefined {\n if (!cookie) return undefined;\n const m = cookie.match(/(^|;)\\s*mcp_session_id\\s*=\\s*([^;]*)/);\n return m ? m[2] : undefined;\n}\n\n/**\n * Update a cached session payload with new data.\n * This is used to persist changes like platformType detection that happen\n * after the initial session creation.\n *\n * @param sessionId - The session ID to update\n * @param updates - Partial payload updates to merge\n * @returns true if the session was found and updated, false otherwise\n */\nexport function updateSessionPayload(sessionId: string, updates: Partial<SessionIdPayload>): boolean {\n const existing = cache.get(sessionId);\n if (existing) {\n // Merge updates into existing payload\n Object.assign(existing, updates);\n // Re-set to refresh TTL\n cache.set(sessionId, existing);\n return true;\n }\n\n // Try to decrypt and update if not in cache\n const decrypted = safeDecrypt(sessionId);\n if (\n isValidSessionPayload(decrypted, (decrypted as SessionIdPayload)?.authSig || '') ||\n isValidPublicSessionPayload(decrypted)\n ) {\n const payload = decrypted as SessionIdPayload;\n Object.assign(payload, updates);\n cache.set(sessionId, payload);\n return true;\n }\n\n return false;\n}\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel KV Session Store
|
|
3
|
+
*
|
|
4
|
+
* Session store implementation using Vercel KV (edge-compatible REST-based key-value store).
|
|
5
|
+
* Uses dynamic import to avoid bundling @vercel/kv for non-Vercel deployments.
|
|
6
|
+
*
|
|
7
|
+
* @see https://vercel.com/docs/storage/vercel-kv
|
|
8
|
+
*/
|
|
9
|
+
import { SessionStore, StoredSession } from './transport-session.types';
|
|
10
|
+
import { FrontMcpLogger } from '../../common/interfaces/logger.interface';
|
|
11
|
+
import type { VercelKvProviderOptions } from '../../common/types/options/redis.options';
|
|
12
|
+
export interface VercelKvSessionConfig {
|
|
13
|
+
/**
|
|
14
|
+
* KV REST API URL
|
|
15
|
+
* @default process.env.KV_REST_API_URL
|
|
16
|
+
*/
|
|
17
|
+
url?: string;
|
|
18
|
+
/**
|
|
19
|
+
* KV REST API Token
|
|
20
|
+
* @default process.env.KV_REST_API_TOKEN
|
|
21
|
+
*/
|
|
22
|
+
token?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Key prefix for session keys
|
|
25
|
+
* @default 'mcp:session:'
|
|
26
|
+
*/
|
|
27
|
+
keyPrefix?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Default TTL in milliseconds for session extension on access
|
|
30
|
+
* @default 3600000 (1 hour)
|
|
31
|
+
*/
|
|
32
|
+
defaultTtlMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Vercel KV-backed session store implementation
|
|
36
|
+
*
|
|
37
|
+
* Provides persistent session storage for edge deployments using Vercel KV.
|
|
38
|
+
* Sessions are stored as JSON with optional TTL.
|
|
39
|
+
*/
|
|
40
|
+
export declare class VercelKvSessionStore implements SessionStore {
|
|
41
|
+
private kv;
|
|
42
|
+
private connectPromise;
|
|
43
|
+
private readonly keyPrefix;
|
|
44
|
+
private readonly defaultTtlMs;
|
|
45
|
+
private readonly logger?;
|
|
46
|
+
private readonly config;
|
|
47
|
+
constructor(config: VercelKvSessionConfig | VercelKvProviderOptions, logger?: FrontMcpLogger);
|
|
48
|
+
/**
|
|
49
|
+
* Connect to Vercel KV
|
|
50
|
+
* Uses dynamic import to avoid bundling @vercel/kv when not used.
|
|
51
|
+
* Thread-safe: concurrent calls will share the same connection promise.
|
|
52
|
+
*/
|
|
53
|
+
connect(): Promise<void>;
|
|
54
|
+
private doConnect;
|
|
55
|
+
private ensureConnected;
|
|
56
|
+
/**
|
|
57
|
+
* Get the full key for a session ID
|
|
58
|
+
* @throws Error if sessionId is empty
|
|
59
|
+
*/
|
|
60
|
+
private key;
|
|
61
|
+
/**
|
|
62
|
+
* Get a stored session by ID
|
|
63
|
+
*
|
|
64
|
+
* Note: Vercel KV doesn't support GETEX, so we use GET + PEXPIRE separately.
|
|
65
|
+
* This is slightly less atomic than Redis GETEX but sufficient for most use cases.
|
|
66
|
+
*/
|
|
67
|
+
get(sessionId: string): Promise<StoredSession | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Store a session with optional TTL
|
|
70
|
+
*/
|
|
71
|
+
set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Delete a session
|
|
74
|
+
*/
|
|
75
|
+
delete(sessionId: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Check if a session exists
|
|
78
|
+
*/
|
|
79
|
+
exists(sessionId: string): Promise<boolean>;
|
|
80
|
+
/**
|
|
81
|
+
* Allocate a new session ID
|
|
82
|
+
*/
|
|
83
|
+
allocId(): string;
|
|
84
|
+
/**
|
|
85
|
+
* Disconnect from Vercel KV
|
|
86
|
+
* Vercel KV uses REST API, so there's no persistent connection to close
|
|
87
|
+
*/
|
|
88
|
+
disconnect(): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Test Vercel KV connection by setting and getting a test key.
|
|
91
|
+
* Useful for validating connection on startup.
|
|
92
|
+
*
|
|
93
|
+
* @returns true if connection is healthy, false otherwise
|
|
94
|
+
*/
|
|
95
|
+
ping(): Promise<boolean>;
|
|
96
|
+
}
|