@frontmcp/sdk 0.5.0 → 0.6.0
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 +3 -3
- package/package.json +8 -19
- 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 +1 -0
- package/src/auth/session/index.js +3 -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/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 +28 -2
- 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 +659 -2
- package/src/common/metadata/front-mcp.metadata.js +3 -1
- package/src/common/metadata/front-mcp.metadata.js.map +1 -1
- 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/tool.metadata.d.ts +33 -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 +10 -2
- 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 +3 -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 +22 -0
- package/src/common/types/options/redis.options.js +45 -0
- package/src/common/types/options/redis.options.js.map +1 -0
- package/src/common/types/options/transport.options.d.ts +84 -0
- package/src/common/types/options/transport.options.js +121 -0
- package/src/common/types/options/transport.options.js.map +1 -0
- package/src/completion/flows/complete.flow.d.ts +17 -2
- 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 +3 -1
- package/src/errors/index.js.map +1 -1
- package/src/errors/mcp.error.d.ts +7 -0
- package/src/errors/mcp.error.js +11 -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 +266 -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/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 +8 -4
- package/src/index.js +20 -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 +17 -2
- 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 +97 -2
- package/src/prompt/flows/prompts-list.flow.d.ts +12 -1
- 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 +22 -3
- package/src/resource/flows/resource-templates-list.flow.d.ts +20 -1
- package/src/resource/flows/resources-list.flow.d.ts +20 -1
- package/src/resource/flows/subscribe-resource.flow.d.ts +17 -2
- package/src/resource/flows/unsubscribe-resource.flow.d.ts +17 -2
- 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/tool/flows/call-tool.flow.d.ts +118 -13
- 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 +25 -11
- package/src/tool/flows/tools-list.flow.js +82 -31
- package/src/tool/flows/tools-list.flow.js.map +1 -1
- package/src/tool/tool.instance.d.ts +1 -4
- 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 +27 -1
- package/src/transport/mcp-handlers/get-prompt-request.handler.d.ts +52 -1
- package/src/transport/mcp-handlers/index.d.ts +413 -7
- 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 +27 -1
- package/src/transport/mcp-handlers/list-resource-templates-request.handler.d.ts +32 -1
- package/src/transport/mcp-handlers/list-resources-request.handler.d.ts +32 -1
- package/src/transport/mcp-handlers/list-tools-request.handler.d.ts +30 -1
- package/src/transport/mcp-handlers/logging-set-level-request.handler.d.ts +20 -0
- package/src/transport/mcp-handlers/read-resource-request.handler.d.ts +27 -1
- package/src/transport/mcp-handlers/subscribe-request.handler.d.ts +20 -0
- package/src/transport/mcp-handlers/unsubscribe-request.handler.d.ts +20 -0
- package/src/transport/transport.registry.d.ts +68 -4
- package/src/transport/transport.registry.js +313 -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,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemorySessionStore = exports.TransportSessionManager = void 0;
|
|
3
|
+
exports.RedisSessionStore = exports.InMemorySessionStore = exports.TransportSessionManager = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
// Transport session architecture
|
|
6
6
|
tslib_1.__exportStar(require("./transport-session.types"), exports);
|
|
7
7
|
var transport_session_manager_1 = require("./transport-session.manager");
|
|
8
8
|
Object.defineProperty(exports, "TransportSessionManager", { enumerable: true, get: function () { return transport_session_manager_1.TransportSessionManager; } });
|
|
9
9
|
Object.defineProperty(exports, "InMemorySessionStore", { enumerable: true, get: function () { return transport_session_manager_1.InMemorySessionStore; } });
|
|
10
|
+
var redis_session_store_1 = require("./redis-session.store");
|
|
11
|
+
Object.defineProperty(exports, "RedisSessionStore", { enumerable: true, get: function () { return redis_session_store_1.RedisSessionStore; } });
|
|
10
12
|
// Authorization store for OAuth flows
|
|
11
13
|
tslib_1.__exportStar(require("./authorization.store"), exports);
|
|
12
14
|
// Authorization vault for stateful sessions
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/auth/session/index.ts"],"names":[],"mappings":";;;;AAAA,iCAAiC;AACjC,oEAA0C;AAC1C,yEAA4F;AAAnF,oIAAA,uBAAuB,OAAA;AAAE,iIAAA,oBAAoB,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/auth/session/index.ts"],"names":[],"mappings":";;;;AAAA,iCAAiC;AACjC,oEAA0C;AAC1C,yEAA4F;AAAnF,oIAAA,uBAAuB,OAAA;AAAE,iIAAA,oBAAoB,OAAA;AACtD,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAE1B,sCAAsC;AACtC,gEAAsC;AAEtC,4CAA4C;AAC5C,gEAAsC","sourcesContent":["// Transport session architecture\nexport * from './transport-session.types';\nexport { TransportSessionManager, InMemorySessionStore } from './transport-session.manager';\nexport { RedisSessionStore } from './redis-session.store';\n\n// Authorization store for OAuth flows\nexport * from './authorization.store';\n\n// Authorization vault for stateful sessions\nexport * from './authorization-vault';\n"]}
|
|
@@ -36,7 +36,7 @@ class Session {
|
|
|
36
36
|
this.user = ctx.user;
|
|
37
37
|
this.claims = ctx.claims;
|
|
38
38
|
// derive token expiration from JWT claims if present (exp in seconds)
|
|
39
|
-
const exp =
|
|
39
|
+
const exp = ctx.claims && typeof ctx.claims['exp'] === 'number' ? Number(ctx.claims['exp']) : undefined;
|
|
40
40
|
if (exp) {
|
|
41
41
|
this.expiresAt = exp > 1e12 ? exp : exp * 1000;
|
|
42
42
|
}
|
|
@@ -68,12 +68,14 @@ class Session {
|
|
|
68
68
|
async getTransportSessionId() {
|
|
69
69
|
if (this.#activeTransportId)
|
|
70
70
|
return this.#activeTransportId;
|
|
71
|
-
const mode = this.scope.metadata.
|
|
71
|
+
const mode = this.scope.metadata.transport?.transportIdMode ?? 'uuid';
|
|
72
72
|
if (typeof mode === 'string') {
|
|
73
73
|
return session_transport_1.TransportIdGenerator.createId(mode);
|
|
74
74
|
}
|
|
75
75
|
else {
|
|
76
|
-
|
|
76
|
+
// Cast to proper function type since Zod's z.function() type is too generic
|
|
77
|
+
const modeFn = mode;
|
|
78
|
+
const modeResult = await modeFn(this.issuer);
|
|
77
79
|
return session_transport_1.TransportIdGenerator.createId(modeResult);
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.base.js","sourceRoot":"","sources":["../../../../../src/auth/session/record/session.base.ts"],"names":[],"mappings":";AAAA,sCAAsC;;;
|
|
1
|
+
{"version":3,"file":"session.base.js","sourceRoot":"","sources":["../../../../../src/auth/session/record/session.base.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/record/session.base.ts\n\nimport type { ProviderSnapshot, SessionMode } from '../session.types';\nimport type { TransportIdMode } from '../../../common';\nimport { TransportIdGenerator } from '../session.transport';\nimport { Scope } from '../../../scope';\n\nexport interface BaseCreateCtx {\n id: string;\n sessionId?: string;\n scope: Scope;\n issuer: string;\n token: string;\n user: SessionUser;\n claims?: SessionClaims;\n createdAt?: number;\n // optional precomputed authorization projections\n authorizedProviders?: Record<string, ProviderSnapshot>;\n authorizedProviderIds?: string[];\n authorizedApps?: Record<string, { id: string; toolIds: string[] }>;\n authorizedAppIds?: string[];\n authorizedResources?: string[];\n scopes?: string[];\n // Scoped tools/prompts maps\n authorizedTools?: Record<string, { executionPath: [string, string]; details?: Record<string, any> }>;\n authorizedToolIds?: string[];\n authorizedPrompts?: Record<string, { executionPath: [string, string]; details?: Record<string, any> }>;\n authorizedPromptIds?: string[];\n}\n\n// TODO: can be extended\nexport interface SessionUser {\n sub?: string;\n name?: string;\n email?: string;\n picture?: string;\n}\n\n// TODO: can be extended\nexport interface SessionClaims {\n [key: string]: any;\n}\n\nexport abstract class Session {\n // ---------------- public immutable data ----------------\n readonly id: string;\n abstract readonly mode: SessionMode;\n readonly createdAt: number;\n readonly scopeId: string;\n readonly user: SessionUser;\n readonly claims?: Record<string, unknown>;\n /** Epoch millis when the bearer token expires (if available). */\n readonly expiresAt?: number;\n\n readonly authorizedProviders: Record<string, ProviderSnapshot>;\n readonly authorizedProviderIds: string[];\n readonly authorizedApps: Record<string, { id: string; toolIds: string[] }>;\n readonly authorizedAppIds: string[];\n readonly authorizedResources: string[];\n readonly scopes?: string[];\n readonly authorizedTools?: Record<string, { executionPath: [string, string]; details?: Record<string, any> }>;\n readonly authorizedToolIds?: string[];\n readonly authorizedPrompts?: Record<string, { executionPath: [string, string]; details?: Record<string, any> }>;\n readonly authorizedPromptIds?: string[];\n\n // ---------------- private/shared ----------------\n #scope: Scope;\n #issuer: string;\n protected token: string;\n\n #activeTransportId?: string;\n\n protected constructor(ctx: BaseCreateCtx) {\n this.id = ctx.id;\n this.createdAt = ctx.createdAt || Date.now();\n this.#scope = ctx.scope;\n this.#issuer = ctx.issuer;\n this.scopeId = ctx.scope.id;\n this.user = ctx.user;\n this.claims = ctx.claims;\n // derive token expiration from JWT claims if present (exp in seconds)\n const exp =\n ctx.claims && typeof (ctx.claims as any)['exp'] === 'number' ? Number((ctx.claims as any)['exp']) : undefined;\n if (exp) {\n this.expiresAt = exp > 1e12 ? exp : exp * 1000;\n }\n // project authorized fields (defaults to empty)\n this.authorizedProviders = ctx.authorizedProviders ?? {};\n this.authorizedProviderIds = ctx.authorizedProviderIds ?? [];\n this.authorizedApps = ctx.authorizedApps ?? {};\n this.authorizedAppIds = ctx.authorizedAppIds ?? [];\n this.authorizedResources = ctx.authorizedResources ?? [];\n this.authorizedTools = ctx.authorizedTools ?? {};\n this.authorizedToolIds = ctx.authorizedToolIds ?? [];\n this.authorizedPrompts = ctx.authorizedPrompts ?? {};\n this.authorizedPromptIds = ctx.authorizedPromptIds ?? [];\n this.token = ctx.token;\n this.#activeTransportId = ctx.sessionId;\n }\n\n /**\n * Get the scope associated with this session.\n * Can be used by subclasses to implement custom scope handling.\n * @protected\n */\n protected get scope(): Scope {\n return this.#scope;\n }\n // ---------------- accessors ----------------\n\n get issuer(): string {\n return this.#issuer;\n }\n\n async getTransportSessionId(): Promise<string> {\n if (this.#activeTransportId) return this.#activeTransportId;\n const mode = this.scope.metadata.transport?.transportIdMode ?? 'uuid';\n if (typeof mode === 'string') {\n return TransportIdGenerator.createId(mode as TransportIdMode);\n } else {\n // Cast to proper function type since Zod's z.function() type is too generic\n const modeFn = mode as (issuer: string) => Promise<TransportIdMode> | TransportIdMode;\n const modeResult = await modeFn(this.issuer);\n return TransportIdGenerator.createId(modeResult);\n }\n }\n\n /**\n * Get the access token for a given provider.\n * Must be implemented in subclasses based on session topology.\n * @protected\n * @param providerId\n */\n abstract getToken(providerId?: string): Promise<string> | string;\n\n // ---------------- scoped view ----------------\n scoped(allowed: string | string[] | ((id: string) => boolean)) {\n const fn =\n typeof allowed === 'function'\n ? allowed\n : Array.isArray(allowed)\n ? (id: string) => allowed.includes(id)\n : (id: string) => id === allowed;\n return new SessionView(this, fn);\n }\n}\n\nexport class SessionView {\n constructor(private readonly parent: Session, private readonly allow: (id: string) => boolean) {}\n\n get id() {\n return this.parent.id;\n }\n get mode() {\n return this.parent.mode;\n }\n get user() {\n return this.parent.user;\n }\n get claims() {\n return this.parent.claims;\n }\n get authorizedApps() {\n return this.parent.authorizedApps;\n }\n\n async getToken(providerId: string) {\n if (!this.allow(providerId)) throw new Error(`scoped_denied:${providerId}`);\n return this.parent.getToken(providerId);\n }\n get transportId() {\n return this.parent.getTransportSessionId;\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Session, type BaseCreateCtx } from './session.base';
|
|
2
|
-
export type StatefulCreateCtx = BaseCreateCtx &
|
|
2
|
+
export type StatefulCreateCtx = BaseCreateCtx & Record<string, never>;
|
|
3
3
|
/**
|
|
4
4
|
* Represents a **stateful session (non-refreshable)** where nested OAuth
|
|
5
5
|
* tokens cannot be refreshed server-side. When a nested provider token
|
|
@@ -13,5 +13,5 @@ export declare class StatelessSession extends Session {
|
|
|
13
13
|
#private;
|
|
14
14
|
readonly mode = "stateless";
|
|
15
15
|
constructor(ctx: StatefulCreateCtx);
|
|
16
|
-
getToken(
|
|
16
|
+
getToken(_providerId?: string): Promise<string> | string;
|
|
17
17
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StatelessSession = void 0;
|
|
4
4
|
const session_base_1 = require("./session.base");
|
|
5
|
+
const mcp_error_1 = require("../../../errors/mcp.error");
|
|
5
6
|
/**
|
|
6
7
|
* Represents a **stateful session (non-refreshable)** where nested OAuth
|
|
7
8
|
* tokens cannot be refreshed server-side. When a nested provider token
|
|
@@ -17,13 +18,14 @@ class StatelessSession extends session_base_1.Session {
|
|
|
17
18
|
* Used to encrypt/decrypt nested provider tokens in #store.
|
|
18
19
|
* @private
|
|
19
20
|
*/
|
|
21
|
+
// eslint-disable-next-line no-unused-private-class-members
|
|
20
22
|
#vault;
|
|
21
23
|
constructor(ctx) {
|
|
22
24
|
super(ctx);
|
|
23
|
-
throw new
|
|
25
|
+
throw new mcp_error_1.InternalMcpError('StatelessSession not yet implemented', 'NOT_IMPLEMENTED');
|
|
24
26
|
}
|
|
25
|
-
getToken(
|
|
26
|
-
throw new
|
|
27
|
+
getToken(_providerId) {
|
|
28
|
+
throw new mcp_error_1.InternalMcpError('Token refresh not supported in stateless mode', 'NOT_IMPLEMENTED');
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
exports.StatelessSession = StatelessSession;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.stateless.js","sourceRoot":"","sources":["../../../../../src/auth/session/record/session.stateless.ts"],"names":[],"mappings":";;;AAAA,iDAA6D;
|
|
1
|
+
{"version":3,"file":"session.stateless.js","sourceRoot":"","sources":["../../../../../src/auth/session/record/session.stateless.ts"],"names":[],"mappings":";;;AAAA,iDAA6D;AAE7D,yDAA6D;AAI7D;;;;;;;;GAQG;AACH,MAAa,gBAAiB,SAAQ,sBAAO;IAClC,IAAI,GAAG,WAAW,CAAC;IAC5B;;;OAGG;IACH,2DAA2D;IAC3D,MAAM,CAAa;IACnB,YAAY,GAAsB;QAChC,KAAK,CAAC,GAAoB,CAAC,CAAC;QAC5B,MAAM,IAAI,4BAAgB,CAAC,sCAAsC,EAAE,iBAAiB,CAAC,CAAC;IACxF,CAAC;IACQ,QAAQ,CAAC,WAAoB;QACpC,MAAM,IAAI,4BAAgB,CAAC,+CAA+C,EAAE,iBAAiB,CAAC,CAAC;IACjG,CAAC;CACF;AAfD,4CAeC","sourcesContent":["import { Session, type BaseCreateCtx } from './session.base';\nimport { TokenVault } from '../token.vault';\nimport { InternalMcpError } from '../../../errors/mcp.error';\n\nexport type StatefulCreateCtx = BaseCreateCtx & Record<string, never>;\n\n/**\n * Represents a **stateful session (non-refreshable)** where nested OAuth\n * tokens cannot be refreshed server-side. When a nested provider token\n * expires, the user must re-authorize to obtain new credentials.\n *\n * Notes:\n * - Simpler flow, but degrades UX when tokens are short-lived.\n * - Prefer the refreshable stateful session for multi-app environments.\n */\nexport class StatelessSession extends Session {\n readonly mode = 'stateless';\n /**\n * Used to encrypt/decrypt nested provider tokens in #store.\n * @private\n */\n // eslint-disable-next-line no-unused-private-class-members\n #vault: TokenVault;\n constructor(ctx: StatefulCreateCtx) {\n super(ctx as BaseCreateCtx);\n throw new InternalMcpError('StatelessSession not yet implemented', 'NOT_IMPLEMENTED');\n }\n override getToken(_providerId?: string): Promise<string> | string {\n throw new InternalMcpError('Token refresh not supported in stateless mode', 'NOT_IMPLEMENTED');\n }\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { SessionStore, StoredSession, RedisConfig } from './transport-session.types';
|
|
3
|
+
import { FrontMcpLogger } from '../../common/interfaces/logger.interface';
|
|
4
|
+
/**
|
|
5
|
+
* Redis-backed session store implementation
|
|
6
|
+
*
|
|
7
|
+
* Provides persistent session storage for distributed deployments.
|
|
8
|
+
* Sessions are stored as JSON with optional TTL.
|
|
9
|
+
*/
|
|
10
|
+
export declare class RedisSessionStore implements SessionStore {
|
|
11
|
+
private readonly redis;
|
|
12
|
+
private readonly keyPrefix;
|
|
13
|
+
private readonly defaultTtlMs;
|
|
14
|
+
private readonly logger?;
|
|
15
|
+
private externalInstance;
|
|
16
|
+
constructor(config: RedisConfig | {
|
|
17
|
+
redis: Redis;
|
|
18
|
+
keyPrefix?: string;
|
|
19
|
+
defaultTtlMs?: number;
|
|
20
|
+
}, logger?: FrontMcpLogger);
|
|
21
|
+
/**
|
|
22
|
+
* Get the full Redis key for a session ID
|
|
23
|
+
* @throws Error if sessionId is empty
|
|
24
|
+
*/
|
|
25
|
+
private key;
|
|
26
|
+
/**
|
|
27
|
+
* Get a stored session by ID
|
|
28
|
+
*
|
|
29
|
+
* Note: Uses atomic GETEX to extend TTL while reading, preventing race conditions
|
|
30
|
+
* where concurrent readers might resurrect expired sessions.
|
|
31
|
+
*/
|
|
32
|
+
get(sessionId: string): Promise<StoredSession | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Store a session with optional TTL
|
|
35
|
+
*/
|
|
36
|
+
set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Delete a session
|
|
39
|
+
*/
|
|
40
|
+
delete(sessionId: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Check if a session exists
|
|
43
|
+
*/
|
|
44
|
+
exists(sessionId: string): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Allocate a new session ID
|
|
47
|
+
*/
|
|
48
|
+
allocId(): string;
|
|
49
|
+
/**
|
|
50
|
+
* Disconnect from Redis (only if we created the connection)
|
|
51
|
+
*/
|
|
52
|
+
disconnect(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Get the underlying Redis client (for advanced use cases)
|
|
55
|
+
*/
|
|
56
|
+
getRedisClient(): Redis;
|
|
57
|
+
/**
|
|
58
|
+
* Test Redis connection by sending a PING command.
|
|
59
|
+
* Useful for validating connection on startup.
|
|
60
|
+
*
|
|
61
|
+
* @returns true if connection is healthy, false otherwise
|
|
62
|
+
*/
|
|
63
|
+
ping(): Promise<boolean>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisSessionStore = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
// auth/session/redis-session.store.ts
|
|
6
|
+
const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const transport_session_types_1 = require("./transport-session.types");
|
|
9
|
+
/**
|
|
10
|
+
* Redis-backed session store implementation
|
|
11
|
+
*
|
|
12
|
+
* Provides persistent session storage for distributed deployments.
|
|
13
|
+
* Sessions are stored as JSON with optional TTL.
|
|
14
|
+
*/
|
|
15
|
+
class RedisSessionStore {
|
|
16
|
+
redis;
|
|
17
|
+
keyPrefix;
|
|
18
|
+
defaultTtlMs;
|
|
19
|
+
logger;
|
|
20
|
+
externalInstance = false;
|
|
21
|
+
constructor(config, logger) {
|
|
22
|
+
// Default TTL of 1 hour for session extension on access
|
|
23
|
+
this.defaultTtlMs = ('defaultTtlMs' in config ? config.defaultTtlMs : undefined) ?? 3600000;
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
if ('redis' in config && config.redis) {
|
|
26
|
+
// Use provided Redis instance
|
|
27
|
+
this.redis = config.redis;
|
|
28
|
+
this.keyPrefix = config.keyPrefix ?? 'mcp:session:';
|
|
29
|
+
this.externalInstance = true;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Create new Redis connection from config
|
|
33
|
+
const redisConfig = config;
|
|
34
|
+
const options = {
|
|
35
|
+
host: redisConfig.host,
|
|
36
|
+
port: redisConfig.port ?? 6379,
|
|
37
|
+
password: redisConfig.password,
|
|
38
|
+
db: redisConfig.db ?? 0,
|
|
39
|
+
};
|
|
40
|
+
if (redisConfig.tls) {
|
|
41
|
+
options.tls = {};
|
|
42
|
+
}
|
|
43
|
+
this.redis = new ioredis_1.default(options);
|
|
44
|
+
this.keyPrefix = redisConfig.keyPrefix ?? 'mcp:session:';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the full Redis key for a session ID
|
|
49
|
+
* @throws Error if sessionId is empty
|
|
50
|
+
*/
|
|
51
|
+
key(sessionId) {
|
|
52
|
+
if (!sessionId || sessionId.trim() === '') {
|
|
53
|
+
throw new Error('[RedisSessionStore] sessionId cannot be empty');
|
|
54
|
+
}
|
|
55
|
+
return `${this.keyPrefix}${sessionId}`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get a stored session by ID
|
|
59
|
+
*
|
|
60
|
+
* Note: Uses atomic GETEX to extend TTL while reading, preventing race conditions
|
|
61
|
+
* where concurrent readers might resurrect expired sessions.
|
|
62
|
+
*/
|
|
63
|
+
async get(sessionId) {
|
|
64
|
+
const key = this.key(sessionId);
|
|
65
|
+
// Use GETEX to atomically get and extend TTL in a single operation
|
|
66
|
+
// This prevents the race where one request deletes expired session
|
|
67
|
+
// while another is trying to extend it
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
// GETEX with EXAT/PXAT is atomic - no race condition possible
|
|
71
|
+
raw = await this.redis.getex(key, 'PX', this.defaultTtlMs);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Fallback for older Redis versions that don't support GETEX
|
|
75
|
+
raw = await this.redis.get(key);
|
|
76
|
+
}
|
|
77
|
+
if (!raw)
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
const result = transport_session_types_1.storedSessionSchema.safeParse(parsed);
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
this.logger?.warn('[RedisSessionStore] Invalid session format', {
|
|
84
|
+
sessionId: sessionId.slice(0, 20),
|
|
85
|
+
errors: result.error.issues.slice(0, 3).map((i) => ({ path: i.path, message: i.message })),
|
|
86
|
+
});
|
|
87
|
+
// Delete invalid session data
|
|
88
|
+
this.delete(sessionId).catch(() => void 0);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const session = result.data;
|
|
92
|
+
// Check application-level expiration (separate from Redis TTL)
|
|
93
|
+
if (session.session.expiresAt && session.session.expiresAt < Date.now()) {
|
|
94
|
+
// Session is logically expired - delete it
|
|
95
|
+
// Note: We await the delete to ensure it completes before returning
|
|
96
|
+
// This prevents race conditions where another read might get the expired session
|
|
97
|
+
await this.delete(sessionId);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
// Bound Redis TTL by session.expiresAt to avoid keeping expired sessions in Redis
|
|
101
|
+
// GETEX may have extended TTL beyond expiresAt, so we shorten it if needed
|
|
102
|
+
if (session.session.expiresAt) {
|
|
103
|
+
const ttlMs = Math.min(this.defaultTtlMs, session.session.expiresAt - Date.now());
|
|
104
|
+
if (ttlMs > 0 && ttlMs < this.defaultTtlMs) {
|
|
105
|
+
// Fire-and-forget - we're only optimizing cache eviction timing
|
|
106
|
+
this.redis.pexpire(key, ttlMs).catch(() => void 0);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Update last accessed timestamp (in the returned object)
|
|
110
|
+
// Note: We don't fire-and-forget a set() here because:
|
|
111
|
+
// 1. GETEX already extended the Redis TTL
|
|
112
|
+
// 2. Fire-and-forget can cause race conditions with deletion
|
|
113
|
+
const updatedSession = {
|
|
114
|
+
...session,
|
|
115
|
+
lastAccessedAt: Date.now(),
|
|
116
|
+
};
|
|
117
|
+
return updatedSession;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.logger?.warn('[RedisSessionStore] Failed to parse session', {
|
|
121
|
+
sessionId: sessionId.slice(0, 20),
|
|
122
|
+
error: error.message,
|
|
123
|
+
});
|
|
124
|
+
// Delete corrupted session payloads to prevent repeated failures
|
|
125
|
+
this.delete(sessionId).catch(() => void 0);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Store a session with optional TTL
|
|
131
|
+
*/
|
|
132
|
+
async set(sessionId, session, ttlMs) {
|
|
133
|
+
const key = this.key(sessionId);
|
|
134
|
+
const value = JSON.stringify(session);
|
|
135
|
+
if (ttlMs && ttlMs > 0) {
|
|
136
|
+
// Use PX for millisecond precision
|
|
137
|
+
await this.redis.set(key, value, 'PX', ttlMs);
|
|
138
|
+
}
|
|
139
|
+
else if (session.session.expiresAt) {
|
|
140
|
+
// Use session's expiration if available
|
|
141
|
+
const ttl = session.session.expiresAt - Date.now();
|
|
142
|
+
if (ttl > 0) {
|
|
143
|
+
await this.redis.set(key, value, 'PX', ttl);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Already expired, but store anyway (will be cleaned up on next access)
|
|
147
|
+
await this.redis.set(key, value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// No TTL - session persists until explicitly deleted
|
|
152
|
+
await this.redis.set(key, value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Delete a session
|
|
157
|
+
*/
|
|
158
|
+
async delete(sessionId) {
|
|
159
|
+
await this.redis.del(this.key(sessionId));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check if a session exists
|
|
163
|
+
*/
|
|
164
|
+
async exists(sessionId) {
|
|
165
|
+
return (await this.redis.exists(this.key(sessionId))) === 1;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Allocate a new session ID
|
|
169
|
+
*/
|
|
170
|
+
allocId() {
|
|
171
|
+
return (0, crypto_1.randomUUID)();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Disconnect from Redis (only if we created the connection)
|
|
175
|
+
*/
|
|
176
|
+
async disconnect() {
|
|
177
|
+
if (!this.externalInstance) {
|
|
178
|
+
await this.redis.quit();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the underlying Redis client (for advanced use cases)
|
|
183
|
+
*/
|
|
184
|
+
getRedisClient() {
|
|
185
|
+
return this.redis;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Test Redis connection by sending a PING command.
|
|
189
|
+
* Useful for validating connection on startup.
|
|
190
|
+
*
|
|
191
|
+
* @returns true if connection is healthy, false otherwise
|
|
192
|
+
*/
|
|
193
|
+
async ping() {
|
|
194
|
+
try {
|
|
195
|
+
const result = await this.redis.ping();
|
|
196
|
+
return result === 'PONG';
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.RedisSessionStore = RedisSessionStore;
|
|
204
|
+
//# sourceMappingURL=redis-session.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-session.store.js","sourceRoot":"","sources":["../../../../src/auth/session/redis-session.store.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/redis-session.store.ts\nimport IoRedis, { Redis, RedisOptions } from 'ioredis';\nimport { randomUUID } from 'crypto';\nimport { SessionStore, StoredSession, RedisConfig, storedSessionSchema } from './transport-session.types';\nimport { FrontMcpLogger } from '../../common/interfaces/logger.interface';\n\n/**\n * Redis-backed session store implementation\n *\n * Provides persistent session storage for distributed deployments.\n * Sessions are stored as JSON with optional TTL.\n */\nexport class RedisSessionStore implements SessionStore {\n private readonly redis: Redis;\n private readonly keyPrefix: string;\n private readonly defaultTtlMs: number;\n private readonly logger?: FrontMcpLogger;\n private externalInstance = false;\n\n constructor(\n config: RedisConfig | { redis: Redis; keyPrefix?: string; defaultTtlMs?: number },\n logger?: FrontMcpLogger,\n ) {\n // Default TTL of 1 hour for session extension on access\n this.defaultTtlMs = ('defaultTtlMs' in config ? config.defaultTtlMs : undefined) ?? 3600000;\n this.logger = logger;\n\n if ('redis' in config && config.redis) {\n // Use provided Redis instance\n this.redis = config.redis;\n this.keyPrefix = config.keyPrefix ?? 'mcp:session:';\n this.externalInstance = true;\n } else {\n // Create new Redis connection from config\n const redisConfig = config as RedisConfig;\n const options: RedisOptions = {\n host: redisConfig.host,\n port: redisConfig.port ?? 6379,\n password: redisConfig.password,\n db: redisConfig.db ?? 0,\n };\n\n if (redisConfig.tls) {\n options.tls = {};\n }\n\n this.redis = new IoRedis(options);\n this.keyPrefix = redisConfig.keyPrefix ?? 'mcp:session:';\n }\n }\n\n /**\n * Get the full Redis key for a session ID\n * @throws Error if sessionId is empty\n */\n private key(sessionId: string): string {\n if (!sessionId || sessionId.trim() === '') {\n throw new Error('[RedisSessionStore] sessionId cannot be empty');\n }\n return `${this.keyPrefix}${sessionId}`;\n }\n\n /**\n * Get a stored session by ID\n *\n * Note: Uses atomic GETEX to extend TTL while reading, preventing race conditions\n * where concurrent readers might resurrect expired sessions.\n */\n async get(sessionId: string): Promise<StoredSession | null> {\n const key = this.key(sessionId);\n\n // Use GETEX to atomically get and extend TTL in a single operation\n // This prevents the race where one request deletes expired session\n // while another is trying to extend it\n let raw: string | null;\n try {\n // GETEX with EXAT/PXAT is atomic - no race condition possible\n raw = await this.redis.getex(key, 'PX', this.defaultTtlMs);\n } catch {\n // Fallback for older Redis versions that don't support GETEX\n raw = await this.redis.get(key);\n }\n\n if (!raw) return null;\n\n try {\n const parsed = JSON.parse(raw);\n const result = storedSessionSchema.safeParse(parsed);\n\n if (!result.success) {\n this.logger?.warn('[RedisSessionStore] Invalid session format', {\n sessionId: sessionId.slice(0, 20),\n errors: result.error.issues.slice(0, 3).map((i) => ({ path: i.path, message: i.message })),\n });\n // Delete invalid session data\n this.delete(sessionId).catch(() => void 0);\n return null;\n }\n\n const session = result.data;\n\n // Check application-level expiration (separate from Redis TTL)\n if (session.session.expiresAt && session.session.expiresAt < Date.now()) {\n // Session is logically expired - delete it\n // Note: We await the delete to ensure it completes before returning\n // This prevents race conditions where another read might get the expired session\n await this.delete(sessionId);\n return null;\n }\n\n // Bound Redis TTL by session.expiresAt to avoid keeping expired sessions in Redis\n // GETEX may have extended TTL beyond expiresAt, so we shorten it if needed\n if (session.session.expiresAt) {\n const ttlMs = Math.min(this.defaultTtlMs, session.session.expiresAt - Date.now());\n if (ttlMs > 0 && ttlMs < this.defaultTtlMs) {\n // Fire-and-forget - we're only optimizing cache eviction timing\n this.redis.pexpire(key, ttlMs).catch(() => void 0);\n }\n }\n\n // Update last accessed timestamp (in the returned object)\n // Note: We don't fire-and-forget a set() here because:\n // 1. GETEX already extended the Redis TTL\n // 2. Fire-and-forget can cause race conditions with deletion\n const updatedSession: StoredSession = {\n ...session,\n lastAccessedAt: Date.now(),\n };\n\n return updatedSession;\n } catch (error) {\n this.logger?.warn('[RedisSessionStore] Failed to parse session', {\n sessionId: sessionId.slice(0, 20),\n error: (error as Error).message,\n });\n // Delete corrupted session payloads to prevent repeated failures\n this.delete(sessionId).catch(() => void 0);\n return null;\n }\n }\n\n /**\n * Store a session with optional TTL\n */\n async set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void> {\n const key = this.key(sessionId);\n const value = JSON.stringify(session);\n\n if (ttlMs && ttlMs > 0) {\n // Use PX for millisecond precision\n await this.redis.set(key, value, 'PX', ttlMs);\n } else if (session.session.expiresAt) {\n // Use session's expiration if available\n const ttl = session.session.expiresAt - Date.now();\n if (ttl > 0) {\n await this.redis.set(key, value, 'PX', ttl);\n } else {\n // Already expired, but store anyway (will be cleaned up on next access)\n await this.redis.set(key, value);\n }\n } else {\n // No TTL - session persists until explicitly deleted\n await this.redis.set(key, value);\n }\n }\n\n /**\n * Delete a session\n */\n async delete(sessionId: string): Promise<void> {\n await this.redis.del(this.key(sessionId));\n }\n\n /**\n * Check if a session exists\n */\n async exists(sessionId: string): Promise<boolean> {\n return (await this.redis.exists(this.key(sessionId))) === 1;\n }\n\n /**\n * Allocate a new session ID\n */\n allocId(): string {\n return randomUUID();\n }\n\n /**\n * Disconnect from Redis (only if we created the connection)\n */\n async disconnect(): Promise<void> {\n if (!this.externalInstance) {\n await this.redis.quit();\n }\n }\n\n /**\n * Get the underlying Redis client (for advanced use cases)\n */\n getRedisClient(): Redis {\n return this.redis;\n }\n\n /**\n * Test Redis connection by sending a PING command.\n * Useful for validating connection on startup.\n *\n * @returns true if connection is healthy, false otherwise\n */\n async ping(): Promise<boolean> {\n try {\n const result = await this.redis.ping();\n return result === 'PONG';\n } catch {\n return false;\n }\n }\n}\n"]}
|
|
@@ -3,10 +3,8 @@ import { StatefulSession } from './record/session.stateful';
|
|
|
3
3
|
import { Scope } from '../../scope';
|
|
4
4
|
import { CreateSessionArgs } from './session.types';
|
|
5
5
|
import { TransparentSession } from './record/session.transparent';
|
|
6
|
-
import { Authorization } from '../../common';
|
|
7
6
|
export declare class SessionService {
|
|
8
7
|
private store;
|
|
9
|
-
keyOf(authorization: Authorization): Promise<void>;
|
|
10
8
|
/**
|
|
11
9
|
* Create and persist a new Session from verified auth data.
|
|
12
10
|
* The returned Session exposes async token helpers, scoped view, and transport JWT helpers.
|
|
@@ -6,14 +6,8 @@ const session_stateless_1 = require("./record/session.stateless");
|
|
|
6
6
|
const session_stateful_1 = require("./record/session.stateful");
|
|
7
7
|
const session_transparent_1 = require("./record/session.transparent");
|
|
8
8
|
const store_1 = require("../../store");
|
|
9
|
-
const session_id_utils_1 = require("./utils/session-id.utils");
|
|
10
9
|
class SessionService {
|
|
11
10
|
store = new store_1.ScopedInMemoryStore();
|
|
12
|
-
async keyOf(authorization) {
|
|
13
|
-
const sessionKey = (0, session_id_utils_1.encryptJson)({ token: authorization.token });
|
|
14
|
-
if (authorization.session) {
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
11
|
/**
|
|
18
12
|
* Create and persist a new Session from verified auth data.
|
|
19
13
|
* The returned Session exposes async token helpers, scoped view, and transport JWT helpers.
|
|
@@ -27,7 +21,7 @@ class SessionService {
|
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
createOrchestratedSession(scope, args) {
|
|
30
|
-
const stateless = scope.metadata.
|
|
24
|
+
const stateless = scope.metadata.transport?.sessionMode === 'stateless';
|
|
31
25
|
if (stateless) {
|
|
32
26
|
return new session_stateless_1.StatelessSession(args);
|
|
33
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.service.js","sourceRoot":"","sources":["../../../../src/auth/session/session.service.ts"],"names":[],"mappings":";;;AAAA,kCAAkC;AAClC,kEAA8D;AAC9D,gEAA4D;AAG5D,sEAAkE;
|
|
1
|
+
{"version":3,"file":"session.service.js","sourceRoot":"","sources":["../../../../src/auth/session/session.service.ts"],"names":[],"mappings":"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","sourcesContent":["// auth/session/session.service.ts\nimport { StatelessSession } from './record/session.stateless';\nimport { StatefulSession } from './record/session.stateful';\nimport { Scope } from '../../scope';\nimport { CreateSessionArgs } from './session.types';\nimport { TransparentSession } from './record/session.transparent';\nimport { ScopedInMemoryStore } from '../../store';\n\nexport class SessionService {\n private store = new ScopedInMemoryStore();\n\n /**\n * Create and persist a new Session from verified auth data.\n * The returned Session exposes async token helpers, scoped view, and transport JWT helpers.\n */\n async createSession(scope: Scope, args: CreateSessionArgs) {\n if (scope.orchestrated) {\n return this.createOrchestratedSession(scope, args);\n } else {\n return this.createTransparentSession(scope, args);\n }\n }\n\n private createOrchestratedSession(scope: Scope, args: CreateSessionArgs) {\n const stateless = scope.metadata.transport?.sessionMode === 'stateless';\n if (stateless) {\n return new StatelessSession(args as any);\n } else {\n return new StatefulSession(args as any);\n }\n }\n\n private createTransparentSession(scope: Scope, args: CreateSessionArgs) {\n const primary = scope.auth;\n\n const apps = scope.apps.getApps();\n const appIds = apps.map((app) => app.id);\n\n // Prefer precomputed projections when provided\n let authorizedApps: Record<string, { id: string; toolIds: string[] }> = args.authorizedApps ?? {};\n if (!args.authorizedApps) {\n authorizedApps = {};\n for (const app of apps) {\n try {\n const toolNames = app.tools.getTools().map((t) => String(t.metadata.name));\n authorizedApps[app.id] = { id: app.id, toolIds: toolNames };\n } catch {\n authorizedApps[app.id] = { id: app.id, toolIds: [] };\n }\n }\n }\n\n // TODO: the authorized resources should be computed from the oauth-protected-resource flow\n // let authorizedResources: string[] = args.authorizedResources ?? [];\n // if (!args.authorizedResources) {\n // authorizedResources = [];\n // }\n\n // Providers snapshot\n let authorizedProviders = args.authorizedProviders;\n let authorizedProviderIds = args.authorizedProviderIds;\n if (!authorizedProviders || !authorizedProviderIds) {\n const expClaim =\n args.claims && typeof (args.claims as any)['exp'] === 'number'\n ? Number((args.claims as any)['exp'])\n : undefined;\n const providerSnapshot = {\n id: primary.id,\n exp: expClaim,\n payload: args.claims ?? {},\n apps: appIds.map((id) => ({ id, toolIds: authorizedApps[id]?.toolIds ?? [] })),\n embedMode: 'plain' as const,\n };\n authorizedProviders = { [primary.id]: providerSnapshot } as any;\n authorizedProviderIds = [primary.id];\n }\n\n // resolve granted scopes from token claims (scope or scp)\n let scopes: string[] = args.scopes ?? [];\n if (!args.scopes) {\n const rawScope = (args.claims && ((args.claims as any)['scope'] ?? (args.claims as any)['scp'])) as unknown;\n scopes = Array.isArray(rawScope)\n ? rawScope.map(String)\n : typeof rawScope === 'string'\n ? rawScope.split(/[\\s,]+/).filter(Boolean)\n : [];\n }\n\n return new TransparentSession({\n apps: appIds,\n id: args.token,\n sessionId: args.sessionId,\n scope,\n user: args.user,\n issuer: primary.issuer,\n token: args.token,\n claims: args.claims,\n authorizedProviders: authorizedProviders as any,\n authorizedProviderIds: authorizedProviderIds as any,\n authorizedApps,\n authorizedAppIds: appIds,\n authorizedResources: [], // TODO: fix\n scopes,\n authorizedTools: args.authorizedTools,\n authorizedToolIds: args.authorizedToolIds,\n authorizedPrompts: args.authorizedPrompts,\n authorizedPromptIds: args.authorizedPromptIds,\n } as any);\n }\n}\n"]}
|
|
@@ -6,6 +6,7 @@ const crypto_1 = require("crypto");
|
|
|
6
6
|
const session_id_utils_1 = require("./utils/session-id.utils");
|
|
7
7
|
const session_crypto_1 = require("./session.crypto");
|
|
8
8
|
const authorization_class_1 = require("../authorization/authorization.class");
|
|
9
|
+
const redis_session_store_1 = require("./redis-session.store");
|
|
9
10
|
/**
|
|
10
11
|
* In-memory session store implementation
|
|
11
12
|
*/
|
|
@@ -93,11 +94,8 @@ class TransportSessionManager {
|
|
|
93
94
|
this.store = new InMemorySessionStore();
|
|
94
95
|
}
|
|
95
96
|
else if (config.store === 'redis') {
|
|
96
|
-
// Redis store
|
|
97
|
-
|
|
98
|
-
// TODO: Implement RedisSessionStore
|
|
99
|
-
console.warn('[TransportSessionManager] Redis store requested but not implemented - falling back to in-memory');
|
|
100
|
-
this.store = new InMemorySessionStore();
|
|
97
|
+
// Instantiate Redis session store
|
|
98
|
+
this.store = new redis_session_store_1.RedisSessionStore(config.config);
|
|
101
99
|
}
|
|
102
100
|
else {
|
|
103
101
|
this.store = new InMemorySessionStore();
|