@sentropic/h2a-cli 0.32.0 → 0.38.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/dist/runtime/mcp-http/app.d.ts +7 -0
- package/dist/runtime/mcp-http/app.d.ts.map +1 -1
- package/dist/runtime/mcp-http/app.js +28 -0
- package/dist/runtime/mcp-http/app.js.map +1 -1
- package/dist/runtime/mcp-http/index.d.ts +4 -0
- package/dist/runtime/mcp-http/index.d.ts.map +1 -1
- package/dist/runtime/mcp-http/index.js +4 -0
- package/dist/runtime/mcp-http/index.js.map +1 -1
- package/dist/runtime/mcp-http/oauth/broker-login.d.ts +50 -0
- package/dist/runtime/mcp-http/oauth/broker-login.d.ts.map +1 -0
- package/dist/runtime/mcp-http/oauth/broker-login.js +41 -0
- package/dist/runtime/mcp-http/oauth/broker-login.js.map +1 -0
- package/dist/runtime/mcp-http/oauth/broker-routes.d.ts +29 -0
- package/dist/runtime/mcp-http/oauth/broker-routes.d.ts.map +1 -0
- package/dist/runtime/mcp-http/oauth/broker-routes.js +46 -0
- package/dist/runtime/mcp-http/oauth/broker-routes.js.map +1 -0
- package/dist/runtime/mcp-http/oauth/config.d.ts +13 -0
- package/dist/runtime/mcp-http/oauth/config.d.ts.map +1 -1
- package/dist/runtime/mcp-http/oauth/config.js +29 -1
- package/dist/runtime/mcp-http/oauth/config.js.map +1 -1
- package/dist/runtime/mcp-http/oauth/oidc-rp.d.ts +56 -0
- package/dist/runtime/mcp-http/oauth/oidc-rp.d.ts.map +1 -0
- package/dist/runtime/mcp-http/oauth/oidc-rp.js +63 -0
- package/dist/runtime/mcp-http/oauth/oidc-rp.js.map +1 -0
- package/dist/runtime/mcp-http/oauth/tenancy.d.ts +3 -0
- package/dist/runtime/mcp-http/oauth/tenancy.d.ts.map +1 -0
- package/dist/runtime/mcp-http/oauth/tenancy.js +18 -0
- package/dist/runtime/mcp-http/oauth/tenancy.js.map +1 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { McpServer } from "../mcp/server.js";
|
|
3
|
+
import type { BrokerLogin } from "./oauth/broker-login.js";
|
|
3
4
|
import { type H2AHostedOAuthConfig } from "./oauth/config.js";
|
|
4
5
|
import type { SingleTenantOAuthProvider } from "./oauth/single-tenant-provider.js";
|
|
5
6
|
export interface HostedAppDeps {
|
|
@@ -7,6 +8,12 @@ export interface HostedAppDeps {
|
|
|
7
8
|
oauthConfig: H2AHostedOAuthConfig;
|
|
8
9
|
/** The in-process h2a MCP dispatch (createMcpServer) — its read-only tools are exposed. */
|
|
9
10
|
h2aMcpServer: McpServer;
|
|
11
|
+
/**
|
|
12
|
+
* EVO-12 P2 (mode 3): when `oauthConfig.brokerMode`, the broker login (built
|
|
13
|
+
* from `oauthConfig.upstream`). Its /authorize delegates the user login to
|
|
14
|
+
* 39-auth instead of the consent secret. Omit for single-tenant.
|
|
15
|
+
*/
|
|
16
|
+
brokerLogin?: BrokerLogin;
|
|
10
17
|
}
|
|
11
18
|
export declare function createHostedApp(deps: HostedAppDeps): Hono;
|
|
12
19
|
//# sourceMappingURL=app.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp-http/app.ts"],"names":[],"mappings":"AAQA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEtF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,yBAAyB,CAAC;IACzC,WAAW,EAAE,oBAAoB,CAAC;IAClC,2FAA2F;IAC3F,YAAY,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp-http/app.ts"],"names":[],"mappings":"AAQA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEtF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,yBAAyB,CAAC;IACzC,WAAW,EAAE,oBAAoB,CAAC;IAClC,2FAA2F;IAC3F,YAAY,EAAE,SAAS,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAsFzD"}
|
|
@@ -8,6 +8,7 @@ import { StreamableHTTPTransport } from "@hono/mcp";
|
|
|
8
8
|
import { bearerAuth } from "@hono/mcp/auth";
|
|
9
9
|
import { Hono } from "hono";
|
|
10
10
|
import { buildHostedMcpServer } from "./hosted-mcp-server.js";
|
|
11
|
+
import { buildBrokerRoutes } from "./oauth/broker-routes.js";
|
|
11
12
|
import { H2A_HOSTED_OAUTH_SCOPE } from "./oauth/config.js";
|
|
12
13
|
import { buildOAuthRoutes } from "./oauth/hono-oauth-router.js";
|
|
13
14
|
export function createHostedApp(deps) {
|
|
@@ -15,6 +16,33 @@ export function createHostedApp(deps) {
|
|
|
15
16
|
const wwwAuthenticateHeader = `Bearer error="Unauthorized", error_description="Unauthorized", resource_metadata="${deps.oauthConfig.resourceMetadataUrl}"`;
|
|
16
17
|
app.get("/healthz", (c) => c.json({ ok: true }));
|
|
17
18
|
app.get("/readyz", (c) => c.json({ ok: true }));
|
|
19
|
+
// EVO-12 P2 (mode 3): in broker mode, the broker's /authorize + /oidc/callback
|
|
20
|
+
// are registered FIRST (Hono first-match wins) so /authorize delegates to
|
|
21
|
+
// 39-auth; /token, /register, well-known still fall through to buildOAuthRoutes.
|
|
22
|
+
if (deps.oauthConfig.brokerMode && deps.brokerLogin) {
|
|
23
|
+
app.route("/", buildBrokerRoutes({
|
|
24
|
+
brokerLogin: deps.brokerLogin,
|
|
25
|
+
issueClaudeaiCode: async (claudeai, _ctx) => {
|
|
26
|
+
const client = await deps.oauthProvider.clientsStore.getClient(claudeai.client_id);
|
|
27
|
+
if (!client)
|
|
28
|
+
throw new Error("unknown client_id");
|
|
29
|
+
const code = await deps.oauthProvider.issueAuthorizationCode(client, {
|
|
30
|
+
redirectUri: claudeai.redirect_uri,
|
|
31
|
+
codeChallenge: claudeai.code_challenge ?? "",
|
|
32
|
+
scopes: [H2A_HOSTED_OAUTH_SCOPE],
|
|
33
|
+
...(claudeai.state ? { state: claudeai.state } : {})
|
|
34
|
+
});
|
|
35
|
+
const redirect = new URL(claudeai.redirect_uri);
|
|
36
|
+
redirect.searchParams.set("code", code);
|
|
37
|
+
if (claudeai.state)
|
|
38
|
+
redirect.searchParams.set("state", claudeai.state);
|
|
39
|
+
return redirect.href;
|
|
40
|
+
// NOTE: per-user-root /mcp serving (binding _ctx.sub/root through the
|
|
41
|
+
// token → serving that tenant's root) is the seed-gated finale — needs
|
|
42
|
+
// provider token metadata + a live 39-auth client to validate.
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
18
46
|
// OAuth AS + protected-resource metadata (unauthenticated) at the root.
|
|
19
47
|
app.route("/", buildOAuthRoutes(deps.oauthProvider, deps.oauthConfig));
|
|
20
48
|
// Bearer gate for /mcp: valid access token AND the read-only scope.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../../src/runtime/mcp-http/app.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../../src/runtime/mcp-http/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,5 +10,9 @@ export { startHostedServer, buildHostedConfigFromEnv, type HostedEnv, type Hoste
|
|
|
10
10
|
export { FileOAuthStore } from "./oauth/file-store.js";
|
|
11
11
|
export { SingleTenantOAuthProvider } from "./oauth/single-tenant-provider.js";
|
|
12
12
|
export { buildOAuthRoutes } from "./oauth/hono-oauth-router.js";
|
|
13
|
+
export { buildUpstreamAuthorizeUrl, exchangeUpstreamCode, type H2AUpstreamOidcConfig, type UpstreamFetch, type UpstreamLogin } from "./oauth/oidc-rp.js";
|
|
14
|
+
export { rootForSub } from "./oauth/tenancy.js";
|
|
15
|
+
export { createBrokerLogin, type BrokerLogin, type BrokerLoginDeps, type BrokerStart, type BrokerComplete } from "./oauth/broker-login.js";
|
|
16
|
+
export { buildBrokerRoutes, type BrokerRoutesDeps } from "./oauth/broker-routes.js";
|
|
13
17
|
export { oauthConfigFromEnv, H2A_HOSTED_OAUTH_SCOPE, type H2AHostedOAuthConfig, type H2AHostedOAuthEnv } from "./oauth/config.js";
|
|
14
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp-http/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp-http/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC"}
|
|
@@ -10,5 +10,9 @@ export { startHostedServer, buildHostedConfigFromEnv } from "./serve.js";
|
|
|
10
10
|
export { FileOAuthStore } from "./oauth/file-store.js";
|
|
11
11
|
export { SingleTenantOAuthProvider } from "./oauth/single-tenant-provider.js";
|
|
12
12
|
export { buildOAuthRoutes } from "./oauth/hono-oauth-router.js";
|
|
13
|
+
export { buildUpstreamAuthorizeUrl, exchangeUpstreamCode } from "./oauth/oidc-rp.js";
|
|
14
|
+
export { rootForSub } from "./oauth/tenancy.js";
|
|
15
|
+
export { createBrokerLogin } from "./oauth/broker-login.js";
|
|
16
|
+
export { buildBrokerRoutes } from "./oauth/broker-routes.js";
|
|
13
17
|
export { oauthConfigFromEnv, H2A_HOSTED_OAUTH_SCOPE } from "./oauth/config.js";
|
|
14
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/runtime/mcp-http/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAsB,MAAM,UAAU,CAAC;AAC/D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EAIzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EAGvB,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/runtime/mcp-http/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAsB,MAAM,UAAU,CAAC;AAC/D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EAIzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EAIrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,iBAAiB,EAKlB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAyB,MAAM,0BAA0B,CAAC;AACpF,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EAGvB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVO-12 P2 (mode 3) — broker-login state machine. The stateful heart of the
|
|
3
|
+
* gateway: on claude.ai's /authorize, `start()` stores the claude.ai request +
|
|
4
|
+
* a fresh upstream PKCE/state and returns the 39-auth /authorize URL to redirect
|
|
5
|
+
* to; on the /oidc/callback, `complete()` looks the pending entry up by state,
|
|
6
|
+
* exchanges the code at 39-auth for the user's `sub`, and resolves that user's
|
|
7
|
+
* per-tenant root. The hono routes are thin wrappers over this.
|
|
8
|
+
*
|
|
9
|
+
* Deps injected (exchange / pkce / randomState / clock) → unit-testable against
|
|
10
|
+
* a mock IdP, no network. Pending entries are single-use + time-boxed.
|
|
11
|
+
*/
|
|
12
|
+
import { type H2AUpstreamOidcConfig, type UpstreamLogin } from "./oidc-rp.js";
|
|
13
|
+
export interface BrokerLoginDeps {
|
|
14
|
+
readonly config: H2AUpstreamOidcConfig;
|
|
15
|
+
/** Bound `exchangeUpstreamCode(config, {code, codeVerifier}, fetch)`. */
|
|
16
|
+
readonly exchange: (code: string, codeVerifier: string) => Promise<UpstreamLogin>;
|
|
17
|
+
/** Base h2a root; the per-user root is `rootForSub(baseRoot, sub)`. */
|
|
18
|
+
readonly baseRoot: string;
|
|
19
|
+
/** Fresh opaque state per login (e.g. `randomToken()`). */
|
|
20
|
+
readonly randomState: () => string;
|
|
21
|
+
/** Fresh PKCE pair for the upstream leg. */
|
|
22
|
+
readonly pkce: () => {
|
|
23
|
+
verifier: string;
|
|
24
|
+
challenge: string;
|
|
25
|
+
};
|
|
26
|
+
readonly now?: () => number;
|
|
27
|
+
/** Pending-login TTL (ms). Default 10 min. */
|
|
28
|
+
readonly maxAgeMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface BrokerStart {
|
|
31
|
+
/** 39-auth /authorize URL to redirect the user to. */
|
|
32
|
+
readonly redirectUrl: string;
|
|
33
|
+
/** The upstream state (also the pending-entry key). */
|
|
34
|
+
readonly state: string;
|
|
35
|
+
}
|
|
36
|
+
export interface BrokerComplete {
|
|
37
|
+
/** The original claude.ai /authorize request, opaque to the broker — to resume issuing the claude.ai code. */
|
|
38
|
+
readonly claudeai: unknown;
|
|
39
|
+
/** The authenticated 39-auth user. */
|
|
40
|
+
readonly sub: string;
|
|
41
|
+
/** That user's per-tenant h2a root. */
|
|
42
|
+
readonly root: string;
|
|
43
|
+
}
|
|
44
|
+
export interface BrokerLogin {
|
|
45
|
+
start(claudeaiParams: unknown): BrokerStart;
|
|
46
|
+
complete(state: string, code: string): Promise<BrokerComplete>;
|
|
47
|
+
pendingCount(): number;
|
|
48
|
+
}
|
|
49
|
+
export declare function createBrokerLogin(deps: BrokerLoginDeps): BrokerLogin;
|
|
50
|
+
//# sourceMappingURL=broker-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-login.d.ts","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/broker-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAA6B,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGzG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAClF,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAC;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,8GAA8G;IAC9G,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,cAAc,EAAE,OAAO,GAAG,WAAW,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/D,YAAY,IAAI,MAAM,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAwBpE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVO-12 P2 (mode 3) — broker-login state machine. The stateful heart of the
|
|
3
|
+
* gateway: on claude.ai's /authorize, `start()` stores the claude.ai request +
|
|
4
|
+
* a fresh upstream PKCE/state and returns the 39-auth /authorize URL to redirect
|
|
5
|
+
* to; on the /oidc/callback, `complete()` looks the pending entry up by state,
|
|
6
|
+
* exchanges the code at 39-auth for the user's `sub`, and resolves that user's
|
|
7
|
+
* per-tenant root. The hono routes are thin wrappers over this.
|
|
8
|
+
*
|
|
9
|
+
* Deps injected (exchange / pkce / randomState / clock) → unit-testable against
|
|
10
|
+
* a mock IdP, no network. Pending entries are single-use + time-boxed.
|
|
11
|
+
*/
|
|
12
|
+
import { buildUpstreamAuthorizeUrl } from "./oidc-rp.js";
|
|
13
|
+
import { rootForSub } from "./tenancy.js";
|
|
14
|
+
export function createBrokerLogin(deps) {
|
|
15
|
+
const now = deps.now ?? Date.now;
|
|
16
|
+
const maxAgeMs = deps.maxAgeMs ?? 600_000;
|
|
17
|
+
const pending = new Map();
|
|
18
|
+
return {
|
|
19
|
+
start(claudeaiParams) {
|
|
20
|
+
const { verifier, challenge } = deps.pkce();
|
|
21
|
+
const state = deps.randomState();
|
|
22
|
+
pending.set(state, { verifier, claudeai: claudeaiParams, at: now() });
|
|
23
|
+
return {
|
|
24
|
+
redirectUrl: buildUpstreamAuthorizeUrl(deps.config, { state, codeChallenge: challenge }),
|
|
25
|
+
state
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
async complete(state, code) {
|
|
29
|
+
const entry = pending.get(state);
|
|
30
|
+
if (!entry)
|
|
31
|
+
throw new Error("broker: unknown state");
|
|
32
|
+
pending.delete(state); // single-use
|
|
33
|
+
if (now() - entry.at > maxAgeMs)
|
|
34
|
+
throw new Error("broker: state expired");
|
|
35
|
+
const login = await deps.exchange(code, entry.verifier);
|
|
36
|
+
return { claudeai: entry.claudeai, sub: login.sub, root: rootForSub(deps.baseRoot, login.sub) };
|
|
37
|
+
},
|
|
38
|
+
pendingCount: () => pending.size
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=broker-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-login.js","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/broker-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,yBAAyB,EAAkD,MAAM,cAAc,CAAC;AACzG,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAuC1C,MAAM,UAAU,iBAAiB,CAAC,IAAqB;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA+D,CAAC;IACvF,OAAO;QACL,KAAK,CAAC,cAAc;YAClB,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACtE,OAAO;gBACL,WAAW,EAAE,yBAAyB,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;gBACxF,KAAK;aACN,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;YACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa;YACpC,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,EAAE,GAAG,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;YAC1E,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;YACxD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAClG,CAAC;QACD,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVO-12 P2 (mode 3) — hono routes for the broker login. Thin wrapper over
|
|
3
|
+
* `createBrokerLogin`: `/authorize` starts a login (302 → 39-auth) instead of
|
|
4
|
+
* the single-tenant consent form; `/oidc/callback` completes it (exchange →
|
|
5
|
+
* sub → per-user root) and hands off to the provider to issue the claude.ai
|
|
6
|
+
* authorization code, then 302s back to claude.ai.
|
|
7
|
+
*
|
|
8
|
+
* `issueClaudeaiCode` is injected (the SingleTenantOAuthProvider's code issuance,
|
|
9
|
+
* bound to the resolved user/root) → routes are testable via `app.request` with
|
|
10
|
+
* a mock IdP, no provider/network.
|
|
11
|
+
*/
|
|
12
|
+
import { Hono } from "hono";
|
|
13
|
+
import type { BrokerLogin } from "./broker-login.js";
|
|
14
|
+
export interface BrokerRoutesDeps {
|
|
15
|
+
readonly brokerLogin: BrokerLogin;
|
|
16
|
+
/**
|
|
17
|
+
* Issue the claude.ai authorization code for the original request, bound to
|
|
18
|
+
* the authenticated user/root, and return the claude.ai redirect URL
|
|
19
|
+
* (`<redirect_uri>?code=…&state=…`).
|
|
20
|
+
*/
|
|
21
|
+
readonly issueClaudeaiCode: (claudeai: Record<string, string>, ctx: {
|
|
22
|
+
sub: string;
|
|
23
|
+
root: string;
|
|
24
|
+
}) => string | Promise<string>;
|
|
25
|
+
/** Callback path registered at 39-auth. Default `/oidc/callback`. */
|
|
26
|
+
readonly callbackPath?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildBrokerRoutes(deps: BrokerRoutesDeps): Hono;
|
|
29
|
+
//# sourceMappingURL=broker-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-routes.d.ts","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/broker-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,CAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAC/B,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,qEAAqE;IACrE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAkC9D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVO-12 P2 (mode 3) — hono routes for the broker login. Thin wrapper over
|
|
3
|
+
* `createBrokerLogin`: `/authorize` starts a login (302 → 39-auth) instead of
|
|
4
|
+
* the single-tenant consent form; `/oidc/callback` completes it (exchange →
|
|
5
|
+
* sub → per-user root) and hands off to the provider to issue the claude.ai
|
|
6
|
+
* authorization code, then 302s back to claude.ai.
|
|
7
|
+
*
|
|
8
|
+
* `issueClaudeaiCode` is injected (the SingleTenantOAuthProvider's code issuance,
|
|
9
|
+
* bound to the resolved user/root) → routes are testable via `app.request` with
|
|
10
|
+
* a mock IdP, no provider/network.
|
|
11
|
+
*/
|
|
12
|
+
import { Hono } from "hono";
|
|
13
|
+
export function buildBrokerRoutes(deps) {
|
|
14
|
+
const router = new Hono();
|
|
15
|
+
const callbackPath = deps.callbackPath ?? "/oidc/callback";
|
|
16
|
+
// claude.ai lands here (DCR+PKCE already done against our self-AS); we redirect
|
|
17
|
+
// the human to 39-auth to actually log in.
|
|
18
|
+
router.get("/authorize", (c) => {
|
|
19
|
+
c.header("Cache-Control", "no-store");
|
|
20
|
+
const claudeai = c.req.query();
|
|
21
|
+
const { redirectUrl } = deps.brokerLogin.start(claudeai);
|
|
22
|
+
return c.redirect(redirectUrl, 302);
|
|
23
|
+
});
|
|
24
|
+
// 39-auth redirects back here with code+state; we exchange, resolve the user's
|
|
25
|
+
// root, issue the claude.ai code, and bounce back to claude.ai.
|
|
26
|
+
router.get(callbackPath, async (c) => {
|
|
27
|
+
const code = c.req.query("code");
|
|
28
|
+
const state = c.req.query("state");
|
|
29
|
+
if (!code || !state) {
|
|
30
|
+
return c.json({ error: "invalid_request", error_description: "missing code/state" }, 400);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const done = await deps.brokerLogin.complete(state, code);
|
|
34
|
+
const redirectUrl = await deps.issueClaudeaiCode(done.claudeai, {
|
|
35
|
+
sub: done.sub,
|
|
36
|
+
root: done.root
|
|
37
|
+
});
|
|
38
|
+
return c.redirect(redirectUrl, 302);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return c.json({ error: "access_denied", error_description: err.message }, 400);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return router;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=broker-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-routes.js","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/broker-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAmB5B,MAAM,UAAU,iBAAiB,CAAC,IAAsB;IACtD,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,gBAAgB,CAAC;IAE3D,gFAAgF;IAChF,2CAA2C;IAC3C,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;QAC7B,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,EAA4B,CAAC;QACzD,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzD,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,+EAA+E;IAC/E,gEAAgE;IAChE,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACpB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC1D,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAkC,EAAE;gBACxF,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAC;YACH,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Scope is read-only (`h2a:read`) — the hosted surface exposes only read tools
|
|
6
6
|
* (DEC-116 key custody; see ../readonly-allowlist).
|
|
7
7
|
*/
|
|
8
|
+
import type { H2AUpstreamOidcConfig } from "./oidc-rp.js";
|
|
8
9
|
export declare const H2A_HOSTED_OAUTH_SCOPE = "h2a:read";
|
|
9
10
|
export interface H2AHostedOAuthEnv {
|
|
10
11
|
PUBLIC_BASE_URL: string;
|
|
@@ -16,6 +17,14 @@ export interface H2AHostedOAuthEnv {
|
|
|
16
17
|
OAUTH_AUTH_CODE_TTL_SECONDS: number;
|
|
17
18
|
H2A_HOSTED_ENROLLMENT_ENABLED?: string;
|
|
18
19
|
NODE_ENV?: string;
|
|
20
|
+
H2A_BROKER_MODE?: string;
|
|
21
|
+
H2A_UPSTREAM_ISSUER?: string;
|
|
22
|
+
H2A_UPSTREAM_AUTHORIZE_URL?: string;
|
|
23
|
+
H2A_UPSTREAM_TOKEN_URL?: string;
|
|
24
|
+
H2A_UPSTREAM_CLIENT_ID?: string;
|
|
25
|
+
H2A_UPSTREAM_CLIENT_SECRET?: string;
|
|
26
|
+
H2A_UPSTREAM_REDIRECT_URI?: string;
|
|
27
|
+
H2A_UPSTREAM_SCOPES?: string;
|
|
19
28
|
}
|
|
20
29
|
export interface H2AHostedOAuthConfig {
|
|
21
30
|
issuerUrl: URL;
|
|
@@ -29,6 +38,10 @@ export interface H2AHostedOAuthConfig {
|
|
|
29
38
|
refreshTokenTtlSeconds: number;
|
|
30
39
|
authCodeTtlSeconds: number;
|
|
31
40
|
nodeEnv: string;
|
|
41
|
+
/** EVO-12 P2: broker mode (delegate login to 39-auth). */
|
|
42
|
+
brokerMode: boolean;
|
|
43
|
+
/** The seeded 39-auth RP config — present iff broker mode. */
|
|
44
|
+
upstream?: H2AUpstreamOidcConfig;
|
|
32
45
|
}
|
|
33
46
|
export declare function parseOAuthCsv(value: string): string[];
|
|
34
47
|
export declare function parseHostedEnrollmentEnabled(value: string | undefined): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,2BAA2B,EAAE,MAAM,CAAC;IACpC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,2BAA2B,EAAE,MAAM,CAAC;IACpC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAIlB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,GAAG,CAAC;IACf,aAAa,EAAE,GAAG,CAAC;IACnB,iBAAiB,EAAE,GAAG,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,UAAU,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAKrD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAG/E;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,iBAAiB,GAAG,oBAAoB,CAwB/E"}
|
|
@@ -24,6 +24,8 @@ export function oauthConfigFromEnv(env) {
|
|
|
24
24
|
if (enrollmentEnabled && !env.OAUTH_CONSENT_SECRET) {
|
|
25
25
|
throw new Error("OAUTH_CONSENT_SECRET is required when H2A_HOSTED_ENROLLMENT_ENABLED=true");
|
|
26
26
|
}
|
|
27
|
+
const brokerMode = env.H2A_BROKER_MODE === "true";
|
|
28
|
+
const upstream = brokerMode ? upstreamFromEnv(env) : undefined;
|
|
27
29
|
return {
|
|
28
30
|
issuerUrl,
|
|
29
31
|
publicBaseUrl,
|
|
@@ -35,7 +37,33 @@ export function oauthConfigFromEnv(env) {
|
|
|
35
37
|
accessTokenTtlSeconds: env.OAUTH_ACCESS_TOKEN_TTL_SECONDS,
|
|
36
38
|
refreshTokenTtlSeconds: env.OAUTH_REFRESH_TOKEN_TTL_SECONDS,
|
|
37
39
|
authCodeTtlSeconds: env.OAUTH_AUTH_CODE_TTL_SECONDS,
|
|
38
|
-
nodeEnv: env.NODE_ENV ?? "development"
|
|
40
|
+
nodeEnv: env.NODE_ENV ?? "development",
|
|
41
|
+
brokerMode,
|
|
42
|
+
...(upstream ? { upstream } : {})
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Parse the seeded 39-auth RP config from env; throws if a field is missing. */
|
|
46
|
+
function upstreamFromEnv(env) {
|
|
47
|
+
const required = {
|
|
48
|
+
issuer: env.H2A_UPSTREAM_ISSUER,
|
|
49
|
+
authorizeUrl: env.H2A_UPSTREAM_AUTHORIZE_URL,
|
|
50
|
+
tokenUrl: env.H2A_UPSTREAM_TOKEN_URL,
|
|
51
|
+
clientId: env.H2A_UPSTREAM_CLIENT_ID,
|
|
52
|
+
clientSecret: env.H2A_UPSTREAM_CLIENT_SECRET,
|
|
53
|
+
redirectUri: env.H2A_UPSTREAM_REDIRECT_URI
|
|
54
|
+
};
|
|
55
|
+
for (const [key, value] of Object.entries(required)) {
|
|
56
|
+
if (!value)
|
|
57
|
+
throw new Error(`H2A_BROKER_MODE=true requires H2A_UPSTREAM_* (missing ${key})`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
issuer: required.issuer,
|
|
61
|
+
authorizeUrl: required.authorizeUrl,
|
|
62
|
+
tokenUrl: required.tokenUrl,
|
|
63
|
+
clientId: required.clientId,
|
|
64
|
+
clientSecret: required.clientSecret,
|
|
65
|
+
redirectUri: required.redirectUri,
|
|
66
|
+
scopes: env.H2A_UPSTREAM_SCOPES ? parseOAuthCsv(env.H2A_UPSTREAM_SCOPES) : ["openid", "profile", "email"]
|
|
39
67
|
};
|
|
40
68
|
}
|
|
41
69
|
//# sourceMappingURL=config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVO-12 P2 (mode 3, gateway broker) — upstream OIDC Relying Party for 39-auth.
|
|
3
|
+
*
|
|
4
|
+
* The hosted shim keeps the DCR + token surface claude.ai needs (self-AS), but
|
|
5
|
+
* delegates the USER login to 39-auth (the sentropic OIDC IdP, live at
|
|
6
|
+
* `…/api/v1/auth/oauth/authorize`): redirect to its `/authorize`, then exchange
|
|
7
|
+
* the code at `/token` and read the authenticated `sub`. The `sub` is what the
|
|
8
|
+
* gateway maps to a per-user h2a root (multi-tenant).
|
|
9
|
+
*
|
|
10
|
+
* Pure + fetch-injected → unit-testable against a mock IdP. The real client
|
|
11
|
+
* (client_id/secret) is seeded operator-side in 39-auth (no DCR there); that
|
|
12
|
+
* seed is the live-integration point, not a code dependency.
|
|
13
|
+
*/
|
|
14
|
+
export interface H2AUpstreamOidcConfig {
|
|
15
|
+
/** 39-auth issuer (informational / id_token `iss` check later). */
|
|
16
|
+
readonly issuer: string;
|
|
17
|
+
/** 39-auth authorization endpoint (e.g. …/api/v1/auth/oauth/authorize). */
|
|
18
|
+
readonly authorizeUrl: string;
|
|
19
|
+
/** 39-auth token endpoint (e.g. …/api/v1/auth/oauth/token). */
|
|
20
|
+
readonly tokenUrl: string;
|
|
21
|
+
/** This gateway's seeded client id at 39-auth. */
|
|
22
|
+
readonly clientId: string;
|
|
23
|
+
/** This gateway's client secret (k8s Secret). */
|
|
24
|
+
readonly clientSecret: string;
|
|
25
|
+
/** The gateway's callback URL registered at 39-auth. */
|
|
26
|
+
readonly redirectUri: string;
|
|
27
|
+
/** Scopes to request (openid required for `sub`). */
|
|
28
|
+
readonly scopes: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
/** Minimal fetch shape (injected for tests; global fetch satisfies it). */
|
|
31
|
+
export type UpstreamFetch = (url: string, init: {
|
|
32
|
+
method: string;
|
|
33
|
+
headers: Record<string, string>;
|
|
34
|
+
body: string;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
ok: boolean;
|
|
37
|
+
status: number;
|
|
38
|
+
json: () => Promise<unknown>;
|
|
39
|
+
}>;
|
|
40
|
+
export interface UpstreamLogin {
|
|
41
|
+
/** The authenticated user — the gateway's per-tenant key. */
|
|
42
|
+
readonly sub: string;
|
|
43
|
+
readonly idToken: string;
|
|
44
|
+
readonly accessToken?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Build the 39-auth `/authorize` URL to redirect the user to (authorization_code + PKCE). */
|
|
47
|
+
export declare function buildUpstreamAuthorizeUrl(config: H2AUpstreamOidcConfig, params: {
|
|
48
|
+
state: string;
|
|
49
|
+
codeChallenge: string;
|
|
50
|
+
}): string;
|
|
51
|
+
/** Exchange an authorization_code (+ PKCE verifier) at 39-auth for the user's `sub`. */
|
|
52
|
+
export declare function exchangeUpstreamCode(config: H2AUpstreamOidcConfig, params: {
|
|
53
|
+
code: string;
|
|
54
|
+
codeVerifier: string;
|
|
55
|
+
}, fetchImpl: UpstreamFetch): Promise<UpstreamLogin>;
|
|
56
|
+
//# sourceMappingURL=oidc-rp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-rp.d.ts","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/oidc-rp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACpE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,8FAA8F;AAC9F,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,qBAAqB,EAC7B,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAC/C,MAAM,CAUR;AAqBD,wFAAwF;AACxF,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,qBAAqB,EAC7B,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAC9C,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,aAAa,CAAC,CA0BxB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Build the 39-auth `/authorize` URL to redirect the user to (authorization_code + PKCE). */
|
|
2
|
+
export function buildUpstreamAuthorizeUrl(config, params) {
|
|
3
|
+
const url = new URL(config.authorizeUrl);
|
|
4
|
+
url.searchParams.set("response_type", "code");
|
|
5
|
+
url.searchParams.set("client_id", config.clientId);
|
|
6
|
+
url.searchParams.set("redirect_uri", config.redirectUri);
|
|
7
|
+
url.searchParams.set("scope", config.scopes.join(" "));
|
|
8
|
+
url.searchParams.set("state", params.state);
|
|
9
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
10
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
11
|
+
return url.href;
|
|
12
|
+
}
|
|
13
|
+
/** Read `sub` from an id_token payload. The token came from our authenticated
|
|
14
|
+
* server-to-server exchange with 39-auth (TLS + client_secret), so the payload
|
|
15
|
+
* is trusted here; verifying the EdDSA signature against 39-auth JWKS is the
|
|
16
|
+
* hardening step (needs JWKS publicly reachable). */
|
|
17
|
+
function subFromIdToken(idToken) {
|
|
18
|
+
const parts = idToken.split(".");
|
|
19
|
+
if (parts.length < 2)
|
|
20
|
+
throw new Error("malformed id_token");
|
|
21
|
+
let payload;
|
|
22
|
+
try {
|
|
23
|
+
payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error("unparseable id_token payload");
|
|
27
|
+
}
|
|
28
|
+
if (typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
29
|
+
throw new Error("id_token has no sub");
|
|
30
|
+
}
|
|
31
|
+
return payload.sub;
|
|
32
|
+
}
|
|
33
|
+
/** Exchange an authorization_code (+ PKCE verifier) at 39-auth for the user's `sub`. */
|
|
34
|
+
export async function exchangeUpstreamCode(config, params, fetchImpl) {
|
|
35
|
+
const body = new URLSearchParams({
|
|
36
|
+
grant_type: "authorization_code",
|
|
37
|
+
code: params.code,
|
|
38
|
+
redirect_uri: config.redirectUri,
|
|
39
|
+
client_id: config.clientId,
|
|
40
|
+
code_verifier: params.codeVerifier
|
|
41
|
+
}).toString();
|
|
42
|
+
const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
|
|
43
|
+
const res = await fetchImpl(config.tokenUrl, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
47
|
+
accept: "application/json",
|
|
48
|
+
authorization: `Basic ${basic}`
|
|
49
|
+
},
|
|
50
|
+
body
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok)
|
|
53
|
+
throw new Error(`upstream token exchange failed: HTTP ${res.status}`);
|
|
54
|
+
const tok = (await res.json());
|
|
55
|
+
if (typeof tok.id_token !== "string")
|
|
56
|
+
throw new Error("upstream token response missing id_token");
|
|
57
|
+
return {
|
|
58
|
+
sub: subFromIdToken(tok.id_token),
|
|
59
|
+
idToken: tok.id_token,
|
|
60
|
+
...(typeof tok.access_token === "string" ? { accessToken: tok.access_token } : {})
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=oidc-rp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-rp.js","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/oidc-rp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenancy.d.ts","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/tenancy.ts"],"names":[],"mappings":"AAYA,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKhE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVO-12 P2 (mode 3, multi-tenant) — map an authenticated 39-auth `sub` to that
|
|
3
|
+
* user's own h2a root, under `<baseRoot>/tenants/<safe(sub)>`. The gateway picks
|
|
4
|
+
* the per-user root after the broker login (so the read-only surface + mirror
|
|
5
|
+
* ingester scope to one user); the single-tenant mode keeps using `baseRoot`.
|
|
6
|
+
*
|
|
7
|
+
* Pure. `safePathSegment` neutralizes path-traversal / unsafe chars in `sub`.
|
|
8
|
+
*/
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { safePathSegment } from "../../local-files/index.js";
|
|
11
|
+
/** Per-tenant h2a root for an authenticated `sub`. */
|
|
12
|
+
export function rootForSub(baseRoot, sub) {
|
|
13
|
+
if (typeof sub !== "string" || sub.length === 0) {
|
|
14
|
+
throw new Error("rootForSub: empty sub");
|
|
15
|
+
}
|
|
16
|
+
return join(baseRoot, "tenants", safePathSegment(sub));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=tenancy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenancy.js","sourceRoot":"","sources":["../../../../src/runtime/mcp-http/oauth/tenancy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,GAAW;IACtD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentropic/h2a-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "Unified CLI surface for h2a hosts and MCP-oriented coordination flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@hono/mcp": "^0.3.0",
|
|
44
44
|
"@hono/node-server": "^2.0.4",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
|
-
"@sentropic/h2a": "^0.
|
|
46
|
+
"@sentropic/h2a": "^0.38.1",
|
|
47
47
|
"hono": "^4.12.23"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|