@ggui-ai/mcp-server 0.2.0-alpha.4 → 0.4.0-rc.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.
Files changed (152) hide show
  1. package/dist/admin-blueprints-transport.d.ts.map +1 -1
  2. package/dist/admin-blueprints-transport.js +2 -1
  3. package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
  4. package/dist/admin-oauth-providers-transport.js +7 -5
  5. package/dist/api-renders-routes.d.ts +85 -0
  6. package/dist/api-renders-routes.d.ts.map +1 -0
  7. package/dist/api-renders-routes.js +372 -0
  8. package/dist/build-mcp.d.ts +2 -2
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +64 -5
  11. package/dist/code-routes.d.ts +47 -0
  12. package/dist/code-routes.d.ts.map +1 -0
  13. package/dist/code-routes.js +81 -0
  14. package/dist/code-store-fs.js +2 -2
  15. package/dist/console-auth.d.ts +10 -10
  16. package/dist/console-auth.d.ts.map +1 -1
  17. package/dist/console-auth.js +5 -5
  18. package/dist/console-blueprint-routes.d.ts +71 -0
  19. package/dist/console-blueprint-routes.d.ts.map +1 -0
  20. package/dist/console-blueprint-routes.js +348 -0
  21. package/dist/console-chat-routes.d.ts +80 -0
  22. package/dist/console-chat-routes.d.ts.map +1 -0
  23. package/dist/console-chat-routes.js +204 -0
  24. package/dist/console-config-routes.d.ts +37 -0
  25. package/dist/console-config-routes.d.ts.map +1 -0
  26. package/dist/console-config-routes.js +91 -0
  27. package/dist/console-headers.d.ts +1 -1
  28. package/dist/console-info-routes.d.ts +84 -0
  29. package/dist/console-info-routes.d.ts.map +1 -0
  30. package/dist/console-info-routes.js +135 -0
  31. package/dist/console-keys-routes.d.ts +50 -0
  32. package/dist/console-keys-routes.d.ts.map +1 -0
  33. package/dist/console-keys-routes.js +222 -0
  34. package/dist/console-llm-keys-routes.d.ts +47 -0
  35. package/dist/console-llm-keys-routes.d.ts.map +1 -0
  36. package/dist/console-llm-keys-routes.js +443 -0
  37. package/dist/console-mcp-tools-routes.d.ts +41 -0
  38. package/dist/console-mcp-tools-routes.d.ts.map +1 -0
  39. package/dist/console-mcp-tools-routes.js +60 -0
  40. package/dist/console-registry-routes.d.ts +66 -0
  41. package/dist/console-registry-routes.d.ts.map +1 -0
  42. package/dist/console-registry-routes.js +276 -0
  43. package/dist/console-session-routes.d.ts +89 -0
  44. package/dist/console-session-routes.d.ts.map +1 -0
  45. package/dist/console-session-routes.js +385 -0
  46. package/dist/console-sessions-routes.d.ts +52 -0
  47. package/dist/console-sessions-routes.d.ts.map +1 -0
  48. package/dist/console-sessions-routes.js +106 -0
  49. package/dist/console-static-routes.d.ts +54 -0
  50. package/dist/console-static-routes.d.ts.map +1 -0
  51. package/dist/console-static-routes.js +190 -0
  52. package/dist/console-theme-routes.d.ts +3 -3
  53. package/dist/console-theme-routes.js +1 -1
  54. package/dist/console-timeline.d.ts +5 -5
  55. package/dist/console-timeline.d.ts.map +1 -1
  56. package/dist/console-timeline.js +27 -26
  57. package/dist/console-welcome.js +2 -2
  58. package/dist/email-login.d.ts.map +1 -1
  59. package/dist/email-login.js +2 -3
  60. package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
  61. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
  62. package/dist/ggui-session-channel/action-ingress.js +228 -0
  63. package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
  64. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
  65. package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
  66. package/dist/ggui-session-channel/internal-types.d.ts +102 -0
  67. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
  68. package/dist/ggui-session-channel/internal-types.js +6 -0
  69. package/dist/ggui-session-channel/outbound.d.ts +81 -0
  70. package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
  71. package/dist/ggui-session-channel/outbound.js +174 -0
  72. package/dist/ggui-session-channel/socket-router.d.ts +38 -0
  73. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
  74. package/dist/ggui-session-channel/socket-router.js +213 -0
  75. package/dist/ggui-session-channel/subscribe.d.ts +165 -0
  76. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
  77. package/dist/ggui-session-channel/subscribe.js +370 -0
  78. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
  79. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
  80. package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
  81. package/dist/ggui-session-channel.d.ts +425 -0
  82. package/dist/ggui-session-channel.d.ts.map +1 -0
  83. package/dist/ggui-session-channel.js +262 -0
  84. package/dist/health-routes.d.ts +76 -0
  85. package/dist/health-routes.d.ts.map +1 -0
  86. package/dist/health-routes.js +145 -0
  87. package/dist/index.d.ts +10 -11
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +8 -9
  90. package/dist/instructions-presets.d.ts +3 -3
  91. package/dist/instructions-presets.js +24 -24
  92. package/dist/llm-backed-negotiator.d.ts +68 -67
  93. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  94. package/dist/llm-backed-negotiator.js +82 -221
  95. package/dist/mcp-apps-outbound.d.ts +47 -48
  96. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  97. package/dist/mcp-apps-outbound.js +154 -177
  98. package/dist/mcp-endpoint-routes.d.ts +88 -0
  99. package/dist/mcp-endpoint-routes.d.ts.map +1 -0
  100. package/dist/mcp-endpoint-routes.js +359 -0
  101. package/dist/mcp-mounts.d.ts +2 -76
  102. package/dist/mcp-mounts.d.ts.map +1 -1
  103. package/dist/mcp-mounts.js +0 -76
  104. package/dist/oauth-as-routes.d.ts +60 -0
  105. package/dist/oauth-as-routes.d.ts.map +1 -0
  106. package/dist/oauth-as-routes.js +82 -0
  107. package/dist/oauth-clients-routes.d.ts +39 -0
  108. package/dist/oauth-clients-routes.d.ts.map +1 -0
  109. package/dist/oauth-clients-routes.js +87 -0
  110. package/dist/oauth-login-types.d.ts +1 -20
  111. package/dist/oauth-login-types.d.ts.map +1 -1
  112. package/dist/oauth-login-types.js +30 -7
  113. package/dist/oauth-login.d.ts.map +1 -1
  114. package/dist/oauth-login.js +3 -2
  115. package/dist/oauth-providers-store.d.ts.map +1 -1
  116. package/dist/oauth-providers-store.js +5 -5
  117. package/dist/oauth.d.ts +9 -8
  118. package/dist/oauth.d.ts.map +1 -1
  119. package/dist/oauth.js +41 -19
  120. package/dist/pairing-transport.d.ts.map +1 -1
  121. package/dist/pairing-transport.js +2 -1
  122. package/dist/request-context.d.ts +2 -2
  123. package/dist/request-context.js +2 -2
  124. package/dist/reserved-validators.d.ts.map +1 -1
  125. package/dist/reserved-validators.js +9 -1
  126. package/dist/route-param.d.ts +9 -0
  127. package/dist/route-param.d.ts.map +1 -0
  128. package/dist/route-param.js +10 -0
  129. package/dist/runtime-bundle-route.d.ts +43 -0
  130. package/dist/runtime-bundle-route.d.ts.map +1 -0
  131. package/dist/runtime-bundle-route.js +80 -0
  132. package/dist/schema-compat.d.ts +64 -62
  133. package/dist/schema-compat.d.ts.map +1 -1
  134. package/dist/schema-compat.js +23 -51
  135. package/dist/server.d.ts +179 -193
  136. package/dist/server.d.ts.map +1 -1
  137. package/dist/server.js +644 -3759
  138. package/dist/storage.d.ts +5 -5
  139. package/dist/storage.d.ts.map +1 -1
  140. package/dist/storage.js +5 -5
  141. package/dist/thread-transport.d.ts.map +1 -1
  142. package/dist/thread-transport.js +4 -3
  143. package/dist/user-session-auth.d.ts +7 -21
  144. package/dist/user-session-auth.d.ts.map +1 -1
  145. package/dist/user-session-auth.js +7 -28
  146. package/package.json +16 -15
  147. package/dist/mcp-apps-inbound.d.ts +0 -86
  148. package/dist/mcp-apps-inbound.d.ts.map +0 -1
  149. package/dist/mcp-apps-inbound.js +0 -283
  150. package/dist/render-channel.d.ts +0 -694
  151. package/dist/render-channel.d.ts.map +0 -1
  152. package/dist/render-channel.js +0 -1775
@@ -0,0 +1,60 @@
1
+ /**
2
+ * OAuth 2.1 + PKCE + DCR route family (per MCP spec 2025-06-18+).
3
+ *
4
+ * GET /.well-known/oauth-protected-resource — RFC 9728 metadata
5
+ * GET <pathPrefix>/:appId/.well-known/oauth-protected-resource
6
+ * — per-app metadata (mounted only with `perAppRouting`)
7
+ * GET /.well-known/oauth-authorization-server — RFC 8414 metadata
8
+ * POST /oauth/register — RFC 7591 DCR
9
+ * GET /oauth/authorize — consent page
10
+ * POST /oauth/authorize — consent submit
11
+ * POST /oauth/token — token exchange
12
+ *
13
+ * The handlers themselves live in `./oauth.ts`; this module owns the
14
+ * Express mounting only. Mounted when the composer enables OAuth —
15
+ * without it, pure-bearer clients still work but OAuth-discovery
16
+ * clients (Claude Desktop, claude.ai, Goose, etc.) bail with
17
+ * "couldn't reach".
18
+ */
19
+ import type { AuthAdapter, PairingService } from "@ggui-ai/mcp-server-core";
20
+ import type { Express } from "express";
21
+ import { type OAuthConfig, type OAuthStorage } from "./oauth.js";
22
+ interface MountOptions {
23
+ /** Express app to mount onto. */
24
+ readonly app: Express;
25
+ /** Resolved OAuth config (issuer override, validateResource, …). */
26
+ readonly oauthConfig: OAuthConfig;
27
+ /** Client / code / token storage the handlers persist into. */
28
+ readonly oauthStorage: OAuthStorage;
29
+ /** Universal MCP endpoint path — the default RFC 9728 resource. */
30
+ readonly universalMcpPath: string;
31
+ /**
32
+ * Per-tenant URL routing shape. When configured, a second
33
+ * well-known endpoint mounts under the same path prefix the
34
+ * per-app `/mcp` handler lives on.
35
+ */
36
+ readonly perAppRouting?: {
37
+ readonly paramName: string;
38
+ readonly pathPrefix?: string;
39
+ };
40
+ /** Auth adapter the consent-submit handler resolves bearers against. */
41
+ readonly auth: AuthAdapter;
42
+ /**
43
+ * Late-bound pairing-service reference. The pairing service is
44
+ * constructed AFTER these routes mount (declaration-order in the
45
+ * composer), so the consent-submit handler reads through a getter
46
+ * on every request. Safe because requests only arrive after
47
+ * `listen()`, which is strictly after composition completes.
48
+ */
49
+ readonly getPairingService: () => PairingService | null;
50
+ }
51
+ /**
52
+ * Mount the OAuth discovery + auth + token endpoints onto the express
53
+ * app. Also flips `trust proxy` on — nginx-ingress + ALB both
54
+ * terminate TLS upstream; without trust-proxy the metadata advertises
55
+ * `http://` instead of `https://` which breaks PKCE (browsers refuse
56
+ * insecure flows). Returns nothing — the routes self-register.
57
+ */
58
+ export declare function mountOAuthAuthorizationServerRoutes(opts: MountOptions): void;
59
+ export {};
60
+ //# sourceMappingURL=oauth-as-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-as-routes.d.ts","sourceRoot":"","sources":["../src/oauth-as-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAOL,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAEpB,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,mEAAmE;IACnE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE;QACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;IACF,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAwE5E"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * OAuth 2.1 + PKCE + DCR route family (per MCP spec 2025-06-18+).
3
+ *
4
+ * GET /.well-known/oauth-protected-resource — RFC 9728 metadata
5
+ * GET <pathPrefix>/:appId/.well-known/oauth-protected-resource
6
+ * — per-app metadata (mounted only with `perAppRouting`)
7
+ * GET /.well-known/oauth-authorization-server — RFC 8414 metadata
8
+ * POST /oauth/register — RFC 7591 DCR
9
+ * GET /oauth/authorize — consent page
10
+ * POST /oauth/authorize — consent submit
11
+ * POST /oauth/token — token exchange
12
+ *
13
+ * The handlers themselves live in `./oauth.ts`; this module owns the
14
+ * Express mounting only. Mounted when the composer enables OAuth —
15
+ * without it, pure-bearer clients still work but OAuth-discovery
16
+ * clients (Claude Desktop, claude.ai, Goose, etc.) bail with
17
+ * "couldn't reach".
18
+ */
19
+ import { handleAuthorizationServerMetadata, handleAuthorizeGet, handleAuthorizePost, handleProtectedResourceMetadata, handleRegister, handleToken, } from "./oauth.js";
20
+ /**
21
+ * Mount the OAuth discovery + auth + token endpoints onto the express
22
+ * app. Also flips `trust proxy` on — nginx-ingress + ALB both
23
+ * terminate TLS upstream; without trust-proxy the metadata advertises
24
+ * `http://` instead of `https://` which breaks PKCE (browsers refuse
25
+ * insecure flows). Returns nothing — the routes self-register.
26
+ */
27
+ export function mountOAuthAuthorizationServerRoutes(opts) {
28
+ const { app, oauthConfig, oauthStorage, universalMcpPath, perAppRouting, auth, getPairingService, } = opts;
29
+ // `trust proxy` so req.protocol + req.host honor X-Forwarded-Proto +
30
+ // X-Forwarded-Host.
31
+ app.set("trust proxy", true);
32
+ app.get("/.well-known/oauth-protected-resource", (req, res) => handleProtectedResourceMetadata(req, res, oauthConfig, universalMcpPath));
33
+ // Per-app protected-resource metadata (RFC 9728 per-resource
34
+ // discovery). When `perAppRouting` is configured,
35
+ // mount a second well-known endpoint under the same path prefix
36
+ // the per-app `/mcp` handler lives on. Each `appId` gets its own
37
+ // metadata document with `resource: ${issuer}${pathPrefix}/${appId}`
38
+ // so claude.ai's discovery flow against `mcp.ggui.ai/apps/<appId>`
39
+ // sees a per-app resource rather than the universal one. The
40
+ // shared `authorization_servers: [issuer]` lets the auth server
41
+ // (also us) issue tokens bound to either resource via RFC 8707
42
+ // resource indicators.
43
+ if (perAppRouting !== undefined) {
44
+ const { paramName, pathPrefix = "" } = perAppRouting;
45
+ // `path-to-regexp` v8 (express@5) removed the `:param(pattern)`
46
+ // inline-regex route syntax — registering one throws at startup.
47
+ // Per-app routes now mount with a PLAIN named param; `paramPattern`
48
+ // is enforced by a single `app.param` validator (registered with
49
+ // the per-app MCP route in the MCP-endpoint family) that 404s any
50
+ // value failing a full-anchored match. Express resolves `app.param`
51
+ // callbacks at dispatch time for any route declaring the param,
52
+ // regardless of registration order, so this well-known route
53
+ // inherits the check even though the validator is wired elsewhere.
54
+ //
55
+ // `req.params` can't be indexed by the runtime `paramName` under
56
+ // the v8 param-key inference, so pin the params type to the plain
57
+ // string dictionary (`ParamsDictionary`) — the legitimate
58
+ // single-value param shape — so the `paramName` lookup resolves
59
+ // to `string`.
60
+ app.get(`${pathPrefix}/:${paramName}/.well-known/oauth-protected-resource`, (req, res) => {
61
+ const appId = req.params[paramName];
62
+ if (typeof appId !== "string" || appId.length === 0) {
63
+ res.status(404).json({ error: "not_found" });
64
+ return;
65
+ }
66
+ handleProtectedResourceMetadata(req, res, oauthConfig, `${pathPrefix}/${appId}`);
67
+ });
68
+ }
69
+ app.get("/.well-known/oauth-authorization-server", (req, res) => handleAuthorizationServerMetadata(req, res, oauthConfig));
70
+ app.post("/oauth/register", (req, res) => {
71
+ void handleRegister(req, res, oauthConfig, oauthStorage);
72
+ });
73
+ app.get("/oauth/authorize", (req, res) => {
74
+ void handleAuthorizeGet(req, res, oauthConfig, oauthStorage);
75
+ });
76
+ app.post("/oauth/authorize", (req, res) => {
77
+ void handleAuthorizePost(req, res, oauthConfig, oauthStorage, auth, getPairingService());
78
+ });
79
+ app.post("/oauth/token", (req, res) => {
80
+ void handleToken(req, res, oauthStorage);
81
+ });
82
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Console OAuth-client management routes (the "Connected Apps"
3
+ * surface). Phase 1 of the Connected Apps slice — list + revoke, no
4
+ * manual create yet.
5
+ *
6
+ * GET /ggui/console/oauth-clients — list (oldest-first by createdAt)
7
+ * DELETE /ggui/console/oauth-clients/:id — revoke (idempotent)
8
+ *
9
+ * Mounted only when both console + OAuth are enabled — there's
10
+ * nothing to manage if OAuth is off, and the management surface
11
+ * belongs to the operator-facing console plane (not the public
12
+ * OAuth metadata endpoints). Same-origin posture: no bearer auth,
13
+ * matches sibling console routes (`/ggui/console/registry`,
14
+ * `/ggui/console/blueprints/cached/...`).
15
+ *
16
+ * Revoke caveat — see OAuthStorage.deleteClient JSDoc: revoke
17
+ * deletes the registration but doesn't invalidate in-flight
18
+ * access tokens (the current paste-key flow has access_token ===
19
+ * paired bearer).
20
+ */
21
+ import type { Express } from "express";
22
+ import type { Logger } from "./logger.js";
23
+ import type { OAuthStorage } from "./oauth.js";
24
+ interface MountOptions {
25
+ /** Express app to mount onto. */
26
+ readonly app: Express;
27
+ /** OAuth storage the management surface lists/revokes against. */
28
+ readonly oauthStorage: OAuthStorage;
29
+ /** Structured logger. */
30
+ readonly logger: Logger;
31
+ }
32
+ /**
33
+ * Mount `GET /ggui/console/oauth-clients` +
34
+ * `DELETE /ggui/console/oauth-clients/:clientId` onto the express
35
+ * app. Returns nothing — the routes self-register.
36
+ */
37
+ export declare function mountOAuthClientsRoutes(opts: MountOptions): void;
38
+ export {};
39
+ //# sourceMappingURL=oauth-clients-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-clients-routes.d.ts","sourceRoot":"","sources":["../src/oauth-clients-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA+DhE"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Console OAuth-client management routes (the "Connected Apps"
3
+ * surface). Phase 1 of the Connected Apps slice — list + revoke, no
4
+ * manual create yet.
5
+ *
6
+ * GET /ggui/console/oauth-clients — list (oldest-first by createdAt)
7
+ * DELETE /ggui/console/oauth-clients/:id — revoke (idempotent)
8
+ *
9
+ * Mounted only when both console + OAuth are enabled — there's
10
+ * nothing to manage if OAuth is off, and the management surface
11
+ * belongs to the operator-facing console plane (not the public
12
+ * OAuth metadata endpoints). Same-origin posture: no bearer auth,
13
+ * matches sibling console routes (`/ggui/console/registry`,
14
+ * `/ggui/console/blueprints/cached/...`).
15
+ *
16
+ * Revoke caveat — see OAuthStorage.deleteClient JSDoc: revoke
17
+ * deletes the registration but doesn't invalidate in-flight
18
+ * access tokens (the current paste-key flow has access_token ===
19
+ * paired bearer).
20
+ */
21
+ import { applyDevtoolSecurityHeaders } from "./console-headers.js";
22
+ /**
23
+ * Mount `GET /ggui/console/oauth-clients` +
24
+ * `DELETE /ggui/console/oauth-clients/:clientId` onto the express
25
+ * app. Returns nothing — the routes self-register.
26
+ */
27
+ export function mountOAuthClientsRoutes(opts) {
28
+ const { app, oauthStorage, logger } = opts;
29
+ app.get("/ggui/console/oauth-clients", async (_req, res) => {
30
+ applyDevtoolSecurityHeaders(res);
31
+ try {
32
+ const clients = await oauthStorage.listClients();
33
+ // Project to a wire shape: explicitly list every field we
34
+ // intend to expose so an unrelated `ClientRecord` field
35
+ // addition doesn't accidentally leak through this endpoint.
36
+ res.json({
37
+ clients: clients.map((c) => ({
38
+ clientId: c.clientId,
39
+ clientName: c.clientName ?? null,
40
+ redirectUris: c.redirectUris,
41
+ createdAt: c.createdAt,
42
+ })),
43
+ });
44
+ }
45
+ catch (err) {
46
+ logger.warn("console_oauth_clients_list_failed", {
47
+ error: String(err),
48
+ });
49
+ res.status(500).json({
50
+ error: "list_failed",
51
+ message: err instanceof Error
52
+ ? `Client list failed — ${err.message}`
53
+ : `Client list failed — ${String(err)}`,
54
+ });
55
+ }
56
+ });
57
+ app.delete("/ggui/console/oauth-clients/:clientId", async (req, res) => {
58
+ applyDevtoolSecurityHeaders(res);
59
+ const clientId = req.params["clientId"];
60
+ if (typeof clientId !== "string" || clientId.length === 0) {
61
+ res.status(400).json({
62
+ error: "missing_client_id",
63
+ message: "clientId required in path segment (e.g. DELETE /ggui/console/oauth-clients/abc123).",
64
+ });
65
+ return;
66
+ }
67
+ try {
68
+ await oauthStorage.deleteClient(clientId);
69
+ // 204 whether the id was present or not — `deleteClient`
70
+ // is idempotent at the storage layer (matches DELETE
71
+ // /blueprints/cached/:id semantics).
72
+ res.status(204).end();
73
+ }
74
+ catch (err) {
75
+ logger.warn("console_oauth_clients_delete_failed", {
76
+ error: String(err),
77
+ clientId,
78
+ });
79
+ res.status(500).json({
80
+ error: "delete_failed",
81
+ message: err instanceof Error
82
+ ? `Client delete failed — ${err.message}`
83
+ : `Client delete failed — ${String(err)}`,
84
+ });
85
+ }
86
+ });
87
+ }
@@ -27,7 +27,6 @@
27
27
  * Provider-subject is the strongest cross-provider primary key the
28
28
  * OAuth spec gives us. Future "link account" flows are explicit.
29
29
  */
30
- import type { AuthResult } from '@ggui-ai/mcp-server-core';
31
30
  /**
32
31
  * The runtime contract every concrete OAuth provider implements.
33
32
  *
@@ -138,23 +137,5 @@ export interface OAuthProviderConfigRecord {
138
137
  */
139
138
  readonly enabled: boolean;
140
139
  }
141
- /**
142
- * The minimal AuthAdapter call shape OAuth callbacks use to mint a
143
- * bearer. `source: 'oauth'` is the canonical provenance string —
144
- * audit hooks branch on this to distinguish OAuth identity from
145
- * pairing-issued bearers.
146
- */
147
- export type OAuthAuthResult = AuthResult & {
148
- source: 'oauth';
149
- };
150
- /**
151
- * Compose the canonical user-id namespace for an OAuth identity.
152
- * `userId = '${providerId}:${providerSubject}'`. Pure helper —
153
- * exported here so every consumer (routes, storage, audit hooks)
154
- * computes the same thing.
155
- */
156
- export declare function composeOAuthUserId(input: {
157
- readonly providerId: string;
158
- readonly providerSubject: string;
159
- }): string;
140
+ export { composeOAuthUserId } from "@ggui-ai/protocol";
160
141
  //# sourceMappingURL=oauth-login-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-login-types.d.ts","sourceRoot":"","sources":["../src/oauth-login-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAC/C;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,GAAG,MAAM,CAET"}
1
+ {"version":3,"file":"oauth-login-types.d.ts","sourceRoot":"","sources":["../src/oauth-login-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAC/C;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAID,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1,9 +1,32 @@
1
1
  /**
2
- * Compose the canonical user-id namespace for an OAuth identity.
3
- * `userId = '${providerId}:${providerSubject}'`. Pure helper —
4
- * exported here so every consumer (routes, storage, audit hooks)
5
- * computes the same thing.
2
+ * OAuth login provider seam.
3
+ *
4
+ * Locks the contract every OAuth-login consumer composes against:
5
+ *
6
+ * - Provider implementations (`oauth-providers/{google,github}.ts`)
7
+ * return one of these per configured provider.
8
+ * - Storage (`oauth-providers-store.ts`) serializes config records
9
+ * and hydrates concrete providers via the registered factory.
10
+ * - Routes (`oauth-login.ts`) iterate the bound providers and call
11
+ * `authorizeUrl` / `exchangeCode` per request.
12
+ * - Console UI (`AdminOAuthProviders.tsx`) lists `providerId` /
13
+ * `displayName` per record so the operator can paste secrets
14
+ * into the right slot.
15
+ *
16
+ * Identity model: a successful OAuth callback mints a bearer for an
17
+ * `Identity` of `{ kind: 'user', userId: '${providerId}:${providerSubject}', roles: [] }`.
18
+ * Email is informational only — providers MAY return it (display in
19
+ * the operator's audit log + the user's `/settings` page), but the
20
+ * identity is always provider-namespaced. No email-based account
21
+ * linking — different providers ⇒ different identities, period.
22
+ *
23
+ * **Why no email-based linking?** A user's OAuth-provider email can
24
+ * change (they switch jobs, providers verify slowly). Linking by
25
+ * email would let an attacker who briefly controls an email at
26
+ * provider X impersonate the same user's account at provider Y.
27
+ * Provider-subject is the strongest cross-provider primary key the
28
+ * OAuth spec gives us. Future "link account" flows are explicit.
6
29
  */
7
- export function composeOAuthUserId(input) {
8
- return `${input.providerId}:${input.providerSubject}`;
9
- }
30
+ // composeOAuthUserId now lives in @ggui-ai/protocol (shared with the
31
+ // OIDC verify adapter, which can't import upward from mcp-server).
32
+ export { composeOAuthUserId } from "@ggui-ai/protocol";
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-login.d.ts","sourceRoot":"","sources":["../src/oauth-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAO1D,OAAO,KAAK,EAEV,SAAS,EACT,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,wBAAwB,wCAAwC,CAAC;AAC9E,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAC3C;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,gCACf,CAAC;AAChC,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAKxD,MAAM,WAAW,uBAAuB;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EACd,aAAa,CAAC,kBAAkB,CAAC,GACjC,CAAC,MAAM,aAAa,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAC3F,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,gCAAgC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CAwWN"}
1
+ {"version":3,"file":"oauth-login.d.ts","sourceRoot":"","sources":["../src/oauth-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAO1D,OAAO,KAAK,EAEV,SAAS,EACT,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,eAAO,MAAM,wBAAwB,wCAAwC,CAAC;AAC9E,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAC3C;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,gCACf,CAAC;AAChC,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAKxD,MAAM,WAAW,uBAAuB;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EACd,aAAa,CAAC,kBAAkB,CAAC,GACjC,CAAC,MAAM,aAAa,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAC3F,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,gCAAgC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CAwWN"}
@@ -1,6 +1,7 @@
1
1
  import { createHash, createHmac, randomBytes, timingSafeEqual, } from 'node:crypto';
2
2
  import { formatUserSessionCookieHeader } from './user-session-auth.js';
3
3
  import { composeOAuthUserId, } from './oauth-login-types.js';
4
+ import { singleParam } from './route-param.js';
4
5
  export const DEFAULT_OAUTH_START_PATH = '/ggui/oauth-login/:providerId/start';
5
6
  export const DEFAULT_OAUTH_CALLBACK_PATH = '/ggui/oauth-login/:providerId/callback';
6
7
  /**
@@ -81,7 +82,7 @@ export function mountOAuthLoginRoutes(app, opts) {
81
82
  });
82
83
  // --- GET /ggui/oauth-login/:providerId/start ---
83
84
  app.get(startPath, async (req, res) => {
84
- const providerId = req.params['providerId'];
85
+ const providerId = singleParam(req.params['providerId']);
85
86
  const reqLogger = opts.logger.child({
86
87
  route: 'GET ' + startPath,
87
88
  providerId: providerId ?? '<missing>',
@@ -131,7 +132,7 @@ export function mountOAuthLoginRoutes(app, opts) {
131
132
  });
132
133
  // --- GET /ggui/oauth-login/:providerId/callback ---
133
134
  app.get(callbackPath, async (req, res) => {
134
- const providerId = req.params['providerId'];
135
+ const providerId = singleParam(req.params['providerId']);
135
136
  const reqLogger = opts.logger.child({
136
137
  route: 'GET ' + callbackPath,
137
138
  providerId: providerId ?? '<missing>',
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-providers-store.d.ts","sourceRoot":"","sources":["../src/oauth-providers-store.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM1C,MAAM,WAAW,0BAA0B;IACzC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC1D,qEAAqE;IACrE,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;IACnE;;;OAGG;IACH,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACzD,uDAAuD;IACvD,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,mEAAmE;IACnE,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAuLD,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,mBAAmB,CA+HrB"}
1
+ {"version":3,"file":"oauth-providers-store.d.ts","sourceRoot":"","sources":["../src/oauth-providers-store.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM1C,MAAM,WAAW,0BAA0B;IACzC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC1D,qEAAqE;IACrE,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;IACnE;;;OAGG;IACH,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACzD,uDAAuD;IACvD,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,mEAAmE;IACnE,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAsLD,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,mBAAmB,CA+HrB"}
@@ -15,6 +15,7 @@
15
15
  import { promises as fs } from 'node:fs';
16
16
  import * as os from 'node:os';
17
17
  import * as path from 'node:path';
18
+ import { isRecord } from '@ggui-ai/protocol';
18
19
  const PROVIDER_ID_RE = /^[a-z][a-z0-9-]*$/;
19
20
  const ENV_KEY_RE = /^GGUI_OAUTH_([A-Z0-9_]+)_CLIENT_ID$/;
20
21
  const FILE_VERSION = '1';
@@ -112,15 +113,14 @@ async function readFileShape(filePath, logger) {
112
113
  });
113
114
  return { version: FILE_VERSION, providers: [] };
114
115
  }
115
- if (!parsed || typeof parsed !== 'object') {
116
+ if (!isRecord(parsed)) {
116
117
  logger.warn('oauth_providers_file_corrupt', {
117
118
  filePath,
118
119
  reason: 'root_not_object',
119
120
  });
120
121
  return { version: FILE_VERSION, providers: [] };
121
122
  }
122
- const obj = parsed;
123
- if (!Array.isArray(obj.providers)) {
123
+ if (!Array.isArray(parsed['providers'])) {
124
124
  logger.warn('oauth_providers_file_corrupt', {
125
125
  filePath,
126
126
  reason: 'providers_not_array',
@@ -128,8 +128,8 @@ async function readFileShape(filePath, logger) {
128
128
  return { version: FILE_VERSION, providers: [] };
129
129
  }
130
130
  const providers = [];
131
- for (const row of obj.providers) {
132
- if (!row || typeof row !== 'object')
131
+ for (const row of parsed['providers']) {
132
+ if (!isRecord(row))
133
133
  continue;
134
134
  const r = row;
135
135
  if (typeof r['providerId'] !== 'string' ||
package/dist/oauth.d.ts CHANGED
@@ -49,8 +49,8 @@
49
49
  * ## Storage
50
50
  *
51
51
  * Auth codes + DCR clients live in-memory ({@link InMemoryOAuthStorage}).
52
- * For multi-replica deployments (e.g. `mcp.ggui.ai` with 2+ pods),
53
- * either:
52
+ * For multi-replica deployments (2+ processes behind one load
53
+ * balancer), either:
54
54
  * - Use nginx-ingress sticky sessions so the same pod handles both
55
55
  * `/oauth/authorize` and `/oauth/token` (current sandbox posture).
56
56
  * - Plug a Redis-backed {@link OAuthStorage} via the
@@ -64,7 +64,7 @@ import type { Request, Response } from 'express';
64
64
  import type { AuthAdapter, PairingService } from '@ggui-ai/mcp-server-core';
65
65
  export interface OAuthConfig {
66
66
  /**
67
- * Public origin of this server (e.g. `https://mcp.ggui.ai`). Used in
67
+ * Public origin of this server (e.g. `https://mcp.example.com`). Used in
68
68
  * discovery metadata, `WWW-Authenticate` headers, and OAuth redirects.
69
69
  * Should NOT have a trailing slash. When absent, the server derives
70
70
  * it from the request's `Host` header — fine for most deployments,
@@ -80,7 +80,7 @@ export interface OAuthConfig {
80
80
  readonly storage?: OAuthStorage;
81
81
  /**
82
82
  * External consent UI to delegate the user-facing approval step to
83
- * (e.g. `https://console.ggui.ai/oauth/consent`). When set,
83
+ * (e.g. `https://console.example.com/oauth/consent`). When set,
84
84
  * `GET /oauth/authorize` returns a 302 to this URL with every OAuth
85
85
  * query param forwarded verbatim — the consent UI then constructs an
86
86
  * HTML form that POSTs back to `<issuer>/oauth/authorize` with the
@@ -104,8 +104,8 @@ export interface OAuthConfig {
104
104
  * MCP endpoint on this deployment, `false` otherwise.
105
105
  *
106
106
  * For ggui this gates two shapes:
107
- * - Universal: `${issuer}` (cloud bare root) or
108
- * `${issuer}${universalMcpPath}` (OSS `/mcp`).
107
+ * - Universal: `${issuer}` (bare-root mount) or
108
+ * `${issuer}${universalMcpPath}` (the conventional `/mcp`).
109
109
  * - Per-app: `${issuer}${perAppRouting.pathPrefix}/<appId>`
110
110
  * where `<appId>` matches `perAppRouting.paramPattern`.
111
111
  *
@@ -269,8 +269,9 @@ export declare function buildWwwAuthenticate(issuerUrl: string, resourcePath?: s
269
269
  * in our case (we host both the resource AND the auth server).
270
270
  *
271
271
  * `mcpPath` (default `/mcp`) is the deployment's universal MCP route.
272
- * Cloud `mcp.ggui.ai` mounts at the bare root `/` so URLs are short
273
- * (the domain already says "mcp"); OSS keeps the conventional `/mcp`.
272
+ * A deployment on a dedicated MCP domain may mount at the bare root
273
+ * `/` so URLs stay short (the domain already says "mcp"); the
274
+ * conventional default is `/mcp`.
274
275
  * The trailing slash is normalized off when the path is `/` so the
275
276
  * resource URL is `${issuer}` (no trailing slash) rather than
276
277
  * `${issuer}/`.
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAO5E,MAAM,WAAW,WAAW;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;IAEhC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3E;AAMD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0BAA0B;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,wDAAwD;IACxD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC9D,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,WAAW,IAAI,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IAChD;;;;;OAKG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAErD,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAQ7D,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAIzD,WAAW,IAAI,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAU/C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMpD;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,YAAY,GAAE,MAAW,GACxB,MAAM,CAIR;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,MAAe,GACvB,IAAI,CASN;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,WAAW,GAClB,IAAI,CAaN;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,WAAW,EACjB,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,GACrC,OAAO,CAAC,IAAI,CAAC,CAoHf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAiFf"}
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
package/dist/oauth.js CHANGED
@@ -49,8 +49,8 @@
49
49
  * ## Storage
50
50
  *
51
51
  * Auth codes + DCR clients live in-memory ({@link InMemoryOAuthStorage}).
52
- * For multi-replica deployments (e.g. `mcp.ggui.ai` with 2+ pods),
53
- * either:
52
+ * For multi-replica deployments (2+ processes behind one load
53
+ * balancer), either:
54
54
  * - Use nginx-ingress sticky sessions so the same pod handles both
55
55
  * `/oauth/authorize` and `/oauth/token` (current sandbox posture).
56
56
  * - Plug a Redis-backed {@link OAuthStorage} via the
@@ -61,7 +61,8 @@
61
61
  * registrations; clients re-register transparently on next failure.
62
62
  */
63
63
  import { createHash, randomBytes } from 'node:crypto';
64
- import { resolveIdentity, UnauthenticatedError } from './auth.js';
64
+ import { isRecord } from '@ggui-ai/protocol';
65
+ import { resolveIdentityFromHeaders, UnauthenticatedError } from './auth.js';
65
66
  export class InMemoryOAuthStorage {
66
67
  codes = new Map();
67
68
  clients = new Map();
@@ -148,6 +149,27 @@ export function buildWwwAuthenticate(issuerUrl, resourcePath = '') {
148
149
  // =============================================================================
149
150
  // PKCE
150
151
  // =============================================================================
152
+ /**
153
+ * Narrow an untrusted params source (express `req.query`, parsed
154
+ * request body) to the string-valued record the OAuth handlers
155
+ * consume. Express query values can be `string | string[] | ParsedQs
156
+ * | ParsedQs[]` (duplicate keys yield arrays under both query
157
+ * parsers) and JSON bodies can carry arbitrary shapes — only single
158
+ * string values are meaningful OAuth params, so anything else is
159
+ * DROPPED at this trust boundary. The downstream required-param
160
+ * validation then rejects the request with a clean 400 instead of
161
+ * flowing a non-string through string-typed code.
162
+ */
163
+ function toStringParams(source) {
164
+ const out = {};
165
+ if (!isRecord(source))
166
+ return out;
167
+ for (const [key, value] of Object.entries(source)) {
168
+ if (typeof value === 'string')
169
+ out[key] = value;
170
+ }
171
+ return out;
172
+ }
151
173
  function sha256Base64Url(input) {
152
174
  return createHash('sha256').update(input).digest('base64url');
153
175
  }
@@ -164,8 +186,9 @@ function verifyPkce(verifier, challenge) {
164
186
  * in our case (we host both the resource AND the auth server).
165
187
  *
166
188
  * `mcpPath` (default `/mcp`) is the deployment's universal MCP route.
167
- * Cloud `mcp.ggui.ai` mounts at the bare root `/` so URLs are short
168
- * (the domain already says "mcp"); OSS keeps the conventional `/mcp`.
189
+ * A deployment on a dedicated MCP domain may mount at the bare root
190
+ * `/` so URLs stay short (the domain already says "mcp"); the
191
+ * conventional default is `/mcp`.
169
192
  * The trailing slash is normalized off when the path is `/` so the
170
193
  * resource URL is `${issuer}` (no trailing slash) rather than
171
194
  * `${issuer}/`.
@@ -206,8 +229,8 @@ export function handleAuthorizationServerMetadata(req, res, config) {
206
229
  * to do PKCE + paste-key gets registered.
207
230
  */
208
231
  export async function handleRegister(req, res, config, storage) {
209
- const body = (req.body ?? {});
210
- const redirectUrisRaw = body.redirect_uris;
232
+ const body = isRecord(req.body) ? req.body : {};
233
+ const redirectUrisRaw = body['redirect_uris'];
211
234
  if (!Array.isArray(redirectUrisRaw) || redirectUrisRaw.length === 0) {
212
235
  res.status(400).json({
213
236
  error: 'invalid_redirect_uri',
@@ -224,7 +247,7 @@ export async function handleRegister(req, res, config, storage) {
224
247
  return;
225
248
  }
226
249
  const clientId = `mcp_client_${randomBytes(16).toString('base64url')}`;
227
- const clientName = typeof body.client_name === 'string' ? body.client_name : undefined;
250
+ const clientName = typeof body['client_name'] === 'string' ? body['client_name'] : undefined;
228
251
  await storage.putClient({
229
252
  clientId,
230
253
  redirectUris,
@@ -256,7 +279,7 @@ export async function handleRegister(req, res, config, storage) {
256
279
  * with the user's pasted key.
257
280
  */
258
281
  export async function handleAuthorizeGet(req, res, config, storage) {
259
- const params = req.query;
282
+ const params = toStringParams(req.query);
260
283
  const issuer = resolveIssuerUrl(req, config.issuerUrl);
261
284
  const v = await validateAuthorizeParams(params, storage, config, issuer);
262
285
  if ('error' in v) {
@@ -301,7 +324,7 @@ export async function handleAuthorizeGet(req, res, config, storage) {
301
324
  * OAuth params).
302
325
  */
303
326
  export async function handleAuthorizePost(req, res, config, storage, auth, pairingService) {
304
- const params = (req.body ?? {});
327
+ const params = toStringParams(req.body);
305
328
  const issuer = resolveIssuerUrl(req, config.issuerUrl);
306
329
  const validation = await validateAuthorizeParams(params, storage, config, issuer);
307
330
  if ('error' in validation) {
@@ -338,15 +361,14 @@ export async function handleAuthorizePost(req, res, config, storage, auth, pairi
338
361
  res.status(400).type('html').send(renderAuthorizePage(params, 'Pair code or API key required'));
339
362
  return;
340
363
  }
341
- // Validate the key by running the adapter's auth path. We synthesize a
342
- // minimal Request shape — the adapter only reads `headers` for bearer
343
- // extraction. Using the real `resolveIdentity()` keeps the validation
344
- // semantics byte-identical to /mcp's auth gate.
345
- const fakeReq = {
346
- headers: { authorization: `Bearer ${apiKey}` },
347
- };
364
+ // Validate the key by running the adapter's auth path. Using the
365
+ // same `resolveIdentityFromHeaders()` the `/mcp` and `/ws` ingress
366
+ // points funnel through keeps the validation semantics
367
+ // byte-identical to /mcp's auth gate — no synthetic Request needed.
348
368
  try {
349
- await resolveIdentity(auth, fakeReq);
369
+ await resolveIdentityFromHeaders(auth, {
370
+ authorization: `Bearer ${apiKey}`,
371
+ });
350
372
  }
351
373
  catch (err) {
352
374
  if (err instanceof UnauthenticatedError) {
@@ -430,7 +452,7 @@ export async function handleAuthorizePost(req, res, config, storage, auth, pairi
430
452
  * 400 status code.
431
453
  */
432
454
  export async function handleToken(req, res, storage) {
433
- const body = (req.body ?? {});
455
+ const body = toStringParams(req.body);
434
456
  if (body['grant_type'] !== 'authorization_code') {
435
457
  res.status(400).json({
436
458
  error: 'unsupported_grant_type',