@plitzi/sdk-server 0.33.1 → 0.34.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 (274) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +283 -8
  3. package/dist/actions.d.ts +62 -0
  4. package/dist/actions.js +12 -0
  5. package/dist/adapters/cloudAdapters.d.ts +72 -0
  6. package/dist/adapters/cloudAdapters.js +303 -0
  7. package/dist/adapters/space/index.d.ts +93 -0
  8. package/dist/adapters/space/index.js +122 -0
  9. package/dist/adapters/space/resolvers.d.ts +69 -0
  10. package/dist/adapters/space/resolvers.js +79 -0
  11. package/dist/adapters/space/space.test.d.ts +1 -0
  12. package/dist/adapters/space/types.d.ts +64 -0
  13. package/dist/adapters/space/types.js +41 -0
  14. package/dist/auth.d.ts +50 -0
  15. package/dist/auth.js +19 -0
  16. package/dist/core/auth/api.d.ts +400 -11
  17. package/dist/core/auth/api.js +1044 -50
  18. package/dist/core/auth/createAuth.d.ts +135 -30
  19. package/dist/core/auth/createAuth.js +96 -24
  20. package/dist/core/auth/credentials.d.ts +9 -3
  21. package/dist/core/auth/credentials.js +10 -4
  22. package/dist/core/auth/csrf.d.ts +89 -0
  23. package/dist/core/auth/csrf.js +234 -0
  24. package/dist/core/auth/csrf.test.d.ts +1 -0
  25. package/dist/core/auth/identity.d.ts +6 -0
  26. package/dist/core/auth/identity.js +37 -8
  27. package/dist/core/auth/lifecycle.test.d.ts +1 -0
  28. package/dist/core/auth/oauth/index.d.ts +8 -0
  29. package/dist/core/auth/oauth/index.js +13 -15
  30. package/dist/core/auth/passwords.d.ts +15 -0
  31. package/dist/core/auth/passwords.js +32 -0
  32. package/dist/core/auth/redirects.d.ts +18 -0
  33. package/dist/core/auth/redirects.js +31 -0
  34. package/dist/core/auth/redirects.test.d.ts +1 -0
  35. package/dist/core/auth/routes.d.ts +4 -2
  36. package/dist/core/auth/routes.js +234 -9
  37. package/dist/core/auth/session.d.ts +11 -0
  38. package/dist/core/auth/session.js +1 -1
  39. package/dist/core/auth/spaceTokens.d.ts +38 -2
  40. package/dist/core/auth/spaceTokens.js +54 -2
  41. package/dist/core/auth/throttle.d.ts +5 -0
  42. package/dist/core/auth/throttle.js +81 -0
  43. package/dist/core/auth/throttle.test.d.ts +1 -0
  44. package/dist/core/auth/tokens.d.ts +83 -12
  45. package/dist/core/auth/tokens.js +51 -9
  46. package/dist/core/auth/totp.d.ts +48 -0
  47. package/dist/core/auth/totp.js +118 -0
  48. package/dist/core/auth/totp.test.d.ts +1 -0
  49. package/dist/core/createServer.d.ts +0 -6
  50. package/dist/core/createServer.js +35 -3
  51. package/dist/core/fontStore.d.ts +33 -0
  52. package/dist/core/fontStore.js +53 -0
  53. package/dist/core/fontStore.test.d.ts +1 -0
  54. package/dist/core/handlers/authMiddleware.js +1 -1
  55. package/dist/core/handlers/authRouteHandlers.d.ts +9 -1
  56. package/dist/core/handlers/authRouteHandlers.js +4 -3
  57. package/dist/core/handlers/csrfMiddleware.d.ts +20 -0
  58. package/dist/core/handlers/csrfMiddleware.js +32 -0
  59. package/dist/core/handlers/oauthRouteHandlers.d.ts +46 -0
  60. package/dist/core/handlers/oauthRouteHandlers.js +159 -0
  61. package/dist/core/handlers/socialRouteHandlers.d.ts +40 -0
  62. package/dist/core/handlers/socialRouteHandlers.js +129 -0
  63. package/dist/core/handlers/socialRouteHandlers.test.d.ts +1 -0
  64. package/dist/core/handlers/types.d.ts +22 -5
  65. package/dist/core/health.d.ts +11 -1
  66. package/dist/core/health.js +27 -3
  67. package/dist/core/http/dispatcher.d.ts +1 -1
  68. package/dist/core/http/dispatcher.js +20 -1
  69. package/dist/core/http/stages/authApi.js +1 -1
  70. package/dist/core/http/stages/fontAssets.d.ts +9 -0
  71. package/dist/core/http/stages/fontAssets.js +27 -0
  72. package/dist/core/http/stages/oauth.d.ts +19 -0
  73. package/dist/core/http/stages/oauth.js +118 -0
  74. package/dist/core/http/types.d.ts +12 -0
  75. package/dist/core/oauth/authorize.d.ts +22 -0
  76. package/dist/core/oauth/authorize.js +254 -0
  77. package/dist/core/oauth/authorize.test.d.ts +1 -0
  78. package/dist/core/oauth/challenge.d.ts +11 -0
  79. package/dist/core/oauth/challenge.js +33 -0
  80. package/dist/core/oauth/consentPage.d.ts +12 -0
  81. package/dist/core/oauth/consentPage.js +131 -0
  82. package/dist/core/oauth/metadata.d.ts +41 -0
  83. package/dist/core/oauth/metadata.js +71 -0
  84. package/dist/core/oauth/params.d.ts +5 -0
  85. package/dist/core/oauth/params.js +5 -0
  86. package/dist/core/oauth/pkce.d.ts +6 -0
  87. package/dist/core/oauth/pkce.js +15 -0
  88. package/dist/core/oauth/records.d.ts +72 -0
  89. package/dist/core/oauth/records.js +49 -0
  90. package/dist/core/oauth/register.d.ts +5 -0
  91. package/dist/core/oauth/register.js +55 -0
  92. package/dist/core/oauth/respond.d.ts +21 -0
  93. package/dist/core/oauth/respond.js +59 -0
  94. package/dist/core/oauth/token.d.ts +14 -0
  95. package/dist/core/oauth/token.js +130 -0
  96. package/dist/core/previewToken.d.ts +20 -0
  97. package/dist/core/previewToken.js +29 -1
  98. package/dist/core/server/pageServer.js +4 -1
  99. package/dist/core/services/action.d.ts +6 -4
  100. package/dist/core/services/action.js +103 -10
  101. package/dist/core/services/registry.js +2 -0
  102. package/dist/core/services/resolve.d.ts +8 -0
  103. package/dist/core/services/resolve.js +12 -1
  104. package/dist/core/staticFiles.js +1 -2
  105. package/dist/handlers.d.ts +7 -1
  106. package/dist/handlers.js +4 -1
  107. package/dist/helpers/buildResponseHelpers.d.ts +8 -0
  108. package/dist/helpers/buildResponseHelpers.js +9 -1
  109. package/dist/helpers/buildServerInfo.d.ts +8 -3
  110. package/dist/helpers/buildServerInfo.js +18 -14
  111. package/dist/helpers/cache/keys.d.ts +13 -0
  112. package/dist/helpers/cache/keys.js +12 -1
  113. package/dist/helpers/onAbort.d.ts +12 -0
  114. package/dist/helpers/onAbort.js +22 -0
  115. package/dist/helpers/onAbort.test.d.ts +1 -0
  116. package/dist/helpers/outboundGuard.d.ts +27 -0
  117. package/dist/helpers/outboundGuard.js +49 -0
  118. package/dist/helpers/outboundGuard.test.d.ts +1 -0
  119. package/dist/helpers/serverLog.d.ts +6 -3
  120. package/dist/helpers/serverLog.js +20 -3
  121. package/dist/index.d.ts +6 -0
  122. package/dist/index.js +9 -4
  123. package/dist/kernel.d.ts +5 -22
  124. package/dist/kernel.js +1 -14
  125. package/dist/modules/actions/{handler.js → connectorWrite.js} +5 -2
  126. package/dist/modules/actions/connectorWrite.test.d.ts +1 -0
  127. package/dist/modules/actions/index.d.ts +40 -0
  128. package/dist/modules/actions/index.js +46 -0
  129. package/dist/modules/actions/moduleFor.d.ts +3 -0
  130. package/dist/modules/actions/moduleFor.js +35 -0
  131. package/dist/modules/actions/moduleFor.test.d.ts +1 -0
  132. package/dist/modules/actions/runtime/check.d.ts +24 -0
  133. package/dist/modules/actions/runtime/check.js +111 -0
  134. package/dist/modules/actions/runtime/check.test.d.ts +1 -0
  135. package/dist/modules/actions/runtime/errors.d.ts +11 -0
  136. package/dist/modules/actions/runtime/errors.js +17 -0
  137. package/dist/modules/actions/runtime/guards.d.ts +126 -0
  138. package/dist/modules/actions/runtime/guards.js +246 -0
  139. package/dist/modules/actions/runtime/guards.test.d.ts +1 -0
  140. package/dist/modules/actions/runtime/kvStore.d.ts +27 -0
  141. package/dist/modules/actions/runtime/kvStore.js +41 -0
  142. package/dist/modules/actions/runtime/kvStore.test.d.ts +1 -0
  143. package/dist/modules/actions/runtime/limits.d.ts +11 -0
  144. package/dist/modules/actions/runtime/limits.js +35 -0
  145. package/dist/modules/actions/runtime/memoryKv.d.ts +2 -0
  146. package/dist/modules/actions/runtime/memoryKv.js +77 -0
  147. package/dist/modules/actions/runtime/namespaceKv.d.ts +9 -0
  148. package/dist/modules/actions/runtime/namespaceKv.js +19 -0
  149. package/dist/modules/actions/runtime/precheck.d.ts +27 -0
  150. package/dist/modules/actions/runtime/precheck.js +45 -0
  151. package/dist/modules/actions/runtime/renderResolver.d.ts +15 -0
  152. package/dist/modules/actions/runtime/renderResolver.js +130 -0
  153. package/dist/modules/actions/runtime/renderResolver.test.d.ts +1 -0
  154. package/dist/modules/actions/runtime/renderShare.d.ts +29 -0
  155. package/dist/modules/actions/runtime/renderShare.js +36 -0
  156. package/dist/modules/actions/runtime/renderShare.test.d.ts +1 -0
  157. package/dist/modules/actions/runtime/report.d.ts +14 -0
  158. package/dist/modules/actions/runtime/report.js +22 -0
  159. package/dist/modules/actions/runtime/runAction.d.ts +12 -0
  160. package/dist/modules/actions/runtime/runAction.js +405 -0
  161. package/dist/modules/actions/runtime/runAction.test.d.ts +1 -0
  162. package/dist/modules/actions/runtime/runLogger.d.ts +31 -0
  163. package/dist/modules/actions/runtime/runLogger.js +66 -0
  164. package/dist/modules/actions/runtime/runLogger.test.d.ts +1 -0
  165. package/dist/modules/actions/runtime/schedule.d.ts +35 -0
  166. package/dist/modules/actions/runtime/schedule.js +76 -0
  167. package/dist/modules/actions/runtime/schedule.test.d.ts +1 -0
  168. package/dist/modules/actions/runtime/scope.d.ts +40 -0
  169. package/dist/modules/actions/runtime/scope.js +103 -0
  170. package/dist/modules/actions/runtime/triggers.d.ts +12 -0
  171. package/dist/modules/actions/runtime/triggers.js +14 -0
  172. package/dist/modules/actions/taskCatalog.d.ts +26 -0
  173. package/dist/modules/actions/taskCatalog.js +24 -0
  174. package/dist/modules/actions/taskCatalog.test.d.ts +1 -0
  175. package/dist/modules/actions/tasks/auth.d.ts +2 -0
  176. package/dist/modules/actions/tasks/auth.js +40 -0
  177. package/dist/modules/actions/tasks/builtins.d.ts +8 -0
  178. package/dist/modules/actions/tasks/builtins.js +24 -0
  179. package/dist/modules/actions/tasks/connector.d.ts +2 -0
  180. package/dist/modules/actions/tasks/connector.js +141 -0
  181. package/dist/modules/actions/tasks/db.d.ts +2 -0
  182. package/dist/modules/actions/tasks/db.js +59 -0
  183. package/dist/modules/actions/tasks/db.test.d.ts +1 -0
  184. package/dist/modules/actions/tasks/flow.d.ts +3 -0
  185. package/dist/modules/actions/tasks/flow.js +109 -0
  186. package/dist/modules/actions/tasks/helpers.d.ts +16 -0
  187. package/dist/modules/actions/tasks/helpers.js +30 -0
  188. package/dist/modules/actions/tasks/http.d.ts +2 -0
  189. package/dist/modules/actions/tasks/http.js +111 -0
  190. package/dist/modules/actions/tasks/http.test.d.ts +1 -0
  191. package/dist/modules/actions/tasks/kv.d.ts +2 -0
  192. package/dist/modules/actions/tasks/kv.js +102 -0
  193. package/dist/modules/actions/tasks/kv.test.d.ts +1 -0
  194. package/dist/modules/actions/tasks/registry.d.ts +10 -0
  195. package/dist/modules/actions/tasks/registry.js +36 -0
  196. package/dist/modules/actions/tasks/transform.d.ts +2 -0
  197. package/dist/modules/actions/tasks/transform.js +34 -0
  198. package/dist/modules/actions/transport/callHandler.d.ts +24 -0
  199. package/dist/modules/actions/transport/callHandler.js +233 -0
  200. package/dist/modules/actions/transport/callHandler.test.d.ts +1 -0
  201. package/dist/modules/actions/transport/cancelHandler.d.ts +23 -0
  202. package/dist/modules/actions/transport/cancelHandler.js +23 -0
  203. package/dist/modules/actions/transport/catalogHandler.d.ts +18 -0
  204. package/dist/modules/actions/transport/catalogHandler.js +21 -0
  205. package/dist/modules/actions/transport/stream.d.ts +23 -0
  206. package/dist/modules/actions/transport/stream.js +100 -0
  207. package/dist/modules/actions/transport/verifySignature.d.ts +16 -0
  208. package/dist/modules/actions/transport/verifySignature.js +72 -0
  209. package/dist/modules/actions/transport/webhookHandler.d.ts +25 -0
  210. package/dist/modules/actions/transport/webhookHandler.js +206 -0
  211. package/dist/modules/actions/transport/webhookHandler.test.d.ts +1 -0
  212. package/dist/modules/actions/types.d.ts +234 -0
  213. package/dist/modules/connectors/engine.js +4 -1
  214. package/dist/modules/connectors/resolver.d.ts +10 -2
  215. package/dist/modules/connectors/resolver.js +8 -3
  216. package/dist/modules/mysql/accounts.d.ts +5 -0
  217. package/dist/modules/mysql/accounts.js +365 -0
  218. package/dist/modules/mysql/accounts.test.d.ts +1 -0
  219. package/dist/modules/mysql/admin.d.ts +58 -0
  220. package/dist/modules/mysql/admin.js +133 -0
  221. package/dist/modules/mysql/config.d.ts +82 -0
  222. package/dist/modules/mysql/config.js +30 -0
  223. package/dist/modules/mysql/identities.d.ts +35 -0
  224. package/dist/modules/mysql/identities.js +43 -0
  225. package/dist/modules/mysql/index.d.ts +67 -0
  226. package/dist/modules/mysql/index.js +62 -0
  227. package/dist/modules/mysql/pool.d.ts +3 -0
  228. package/dist/modules/mysql/pool.js +88 -0
  229. package/dist/modules/mysql/query.d.ts +12 -0
  230. package/dist/modules/mysql/query.js +21 -0
  231. package/dist/modules/mysql/schema.d.ts +43 -0
  232. package/dist/modules/mysql/schema.js +340 -0
  233. package/dist/modules/mysql/schema.test.d.ts +1 -0
  234. package/dist/modules/mysql/sessions.d.ts +33 -0
  235. package/dist/modules/mysql/sessions.js +46 -0
  236. package/dist/modules/mysql/spaceTokens.d.ts +11 -0
  237. package/dist/modules/mysql/spaceTokens.js +59 -0
  238. package/dist/modules/mysql/store.integration.test.d.ts +1 -0
  239. package/dist/modules/rsc/connectorRscData.d.ts +25 -0
  240. package/dist/modules/rsc/connectorRscData.js +51 -0
  241. package/dist/modules/rsc/connectorRscData.test.d.ts +1 -0
  242. package/dist/modules/rsc/handler.js +16 -3
  243. package/dist/modules/rsc/matchRscPage.d.ts +14 -0
  244. package/dist/modules/rsc/matchRscPage.js +26 -0
  245. package/dist/modules/rsc/resolveRscData.d.ts +14 -1
  246. package/dist/modules/rsc/resolveRscData.js +23 -34
  247. package/dist/modules/ssr/Component.d.ts +6 -2
  248. package/dist/modules/ssr/Component.js +3 -1
  249. package/dist/modules/ssr/draftSession.test.d.ts +1 -0
  250. package/dist/modules/ssr/handler.js +15 -2
  251. package/dist/modules/ssr/loadPluginComponents.js +22 -5
  252. package/dist/modules/ssr/prepareRender.js +87 -8
  253. package/dist/modules/ssr/prepareRender.test.d.ts +1 -0
  254. package/dist/modules/ssr/preview.d.ts +21 -8
  255. package/dist/modules/ssr/preview.js +38 -14
  256. package/dist/modules/ssr/registerExternalPlugins.js +2 -2
  257. package/dist/modules/ssr/resolvePageSeo.d.ts +18 -0
  258. package/dist/modules/ssr/resolvePageSeo.js +26 -0
  259. package/dist/modules/ssr/resolvePageSeo.test.d.ts +1 -0
  260. package/dist/modules/ssr/template.test.d.ts +1 -0
  261. package/dist/modules/ssr/views/template.ejs +71 -5
  262. package/dist/mysql.d.ts +26 -0
  263. package/dist/mysql.js +4 -0
  264. package/dist/oauth.d.ts +24 -0
  265. package/dist/oauth.js +10 -0
  266. package/dist/plugins/compile.d.ts +1 -0
  267. package/dist/plugins/compile.js +37 -3
  268. package/dist/plugins/manager.d.ts +40 -0
  269. package/dist/plugins/manager.js +113 -13
  270. package/dist/plugins/manager.test.d.ts +1 -0
  271. package/package.json +43 -14
  272. package/skills/plitzi-authoring/SKILL.md +252 -0
  273. /package/dist/{modules/actions/handler.test.d.ts → adapters/cloudAdapters.test.d.ts} +0 -0
  274. /package/dist/modules/actions/{handler.d.ts → connectorWrite.d.ts} +0 -0
@@ -0,0 +1,20 @@
1
+ import { AuthedRequest, JsonResponse } from './types';
2
+ import { Csrf } from '../auth/csrf';
3
+ import { SessionCookies } from '../auth/session';
4
+ export interface CsrfMiddlewareOptions {
5
+ /** Paths this deployment answers that must not be asked for a token — a payment gateway's webhook, say. */
6
+ exempt?: (path: string) => boolean;
7
+ /** `message` on some APIs, `error` on others. Matches whatever the rest of the deployment answers with. */
8
+ errorKey?: 'message' | 'error';
9
+ }
10
+ /**
11
+ * The CSRF check as middleware, for a deployment with a router.
12
+ *
13
+ * No framework is imported and none is assumed: it reads the handful of properties `AuthedRequest` describes, so
14
+ * an Express, Connect or Koa request satisfies it unchanged. Mount it after whatever parses bodies and cookies —
15
+ * a token posted in a form field cannot be read before the body is.
16
+ *
17
+ * A webhook is the case worth thinking about: it is a cross-site POST by design, it authenticates with a signature
18
+ * rather than a cookie, and it will never carry a token. Exempt it by path.
19
+ */
20
+ export declare const createCsrfMiddleware: (csrf: Csrf, cookies: SessionCookies, { exempt, errorKey }?: CsrfMiddlewareOptions) => (req: AuthedRequest, res: JsonResponse, next: () => void) => void;
@@ -0,0 +1,32 @@
1
+ import { csrfFailureMessage } from "../auth/csrf.js";
2
+ //#region src/core/handlers/csrfMiddleware.ts
3
+ /**
4
+ * The CSRF check as middleware, for a deployment with a router.
5
+ *
6
+ * No framework is imported and none is assumed: it reads the handful of properties `AuthedRequest` describes, so
7
+ * an Express, Connect or Koa request satisfies it unchanged. Mount it after whatever parses bodies and cookies —
8
+ * a token posted in a form field cannot be read before the body is.
9
+ *
10
+ * A webhook is the case worth thinking about: it is a cross-site POST by design, it authenticates with a signature
11
+ * rather than a cookie, and it will never carry a token. Exempt it by path.
12
+ */
13
+ var createCsrfMiddleware = (csrf, cookies, { exempt, errorKey = "message" } = {}) => (req, res, next) => {
14
+ if (exempt?.(req.path) || !csrf.required({
15
+ ...req,
16
+ method: req.method
17
+ })) {
18
+ next();
19
+ return;
20
+ }
21
+ const result = csrf.verify(req, cookies.resolveSessionToken(req));
22
+ if (result.ok) {
23
+ next();
24
+ return;
25
+ }
26
+ res.status(403).json({
27
+ [errorKey]: csrfFailureMessage[result.reason],
28
+ reason: result.reason
29
+ });
30
+ };
31
+ //#endregion
32
+ export { createCsrfMiddleware };
@@ -0,0 +1,46 @@
1
+ import { AuthedRequest, HttpRoute, RouterLike } from './types';
2
+ import { OAuthConfig } from '@plitzi/sdk-shared';
3
+ /**
4
+ * A response that can send bytes, which OAuth needs and `JsonResponse` does not offer.
5
+ *
6
+ * The grant screen is HTML and the code hand-off is a 302, so this asks for the two things a JSON-only response
7
+ * cannot do. Express satisfies it as it stands; anything else needs a `send`, a `setHeader` and a status.
8
+ */
9
+ export interface OAuthResponse {
10
+ status: (code: number) => OAuthResponse;
11
+ setHeader: (name: string, value: string | string[]) => unknown;
12
+ send: (body: string) => unknown;
13
+ end: () => unknown;
14
+ }
15
+ export type OAuthRouteHandler = (req: AuthedRequest, res: OAuthResponse) => Promise<void>;
16
+ export interface OAuthRouteHandlersOptions {
17
+ config: OAuthConfig;
18
+ onError?: (error: unknown, route: {
19
+ method: string;
20
+ path: string;
21
+ }) => void;
22
+ }
23
+ /**
24
+ * OAuth 2.1 authorization as ordinary routes, for a host that runs a router rather than the render pipeline.
25
+ *
26
+ * The same handlers the pipeline stage calls — this is the mounting, not a second implementation. A deployment
27
+ * whose API is an Express app mounts these; one whose MCP server is the SDK's own pipeline mounts the stage. Both
28
+ * grant the credential `config.adapters.issueToken` mints, and THAT is what distinguishes one mounting from
29
+ * another: a space-scoped agent token for a connector, a user session for a native client.
30
+ *
31
+ * `paths` are relative, so the host decides where the set answers. The discovery documents must be reachable at
32
+ * the well-known paths off the ORIGIN root, so a host that mounts this under a prefix serves those two itself.
33
+ */
34
+ export declare const createOAuthRouteHandlers: ({ config, onError }: OAuthRouteHandlersOptions) => HttpRoute<OAuthRouteHandler>[];
35
+ /**
36
+ * Where the set answers, relative to wherever it is mounted.
37
+ *
38
+ * Exported so a host's auth policy can DERIVE which paths are public rather than restating them: every one of
39
+ * these is reached without a session by definition — `/authorize` is what a signed-out person opens, `/token` is
40
+ * authenticated by PKCE, and the discovery documents are what a client reads before it has anything at all. A
41
+ * restated list is one that drifts, and drifting the wrong way here means an authorization server nobody can
42
+ * start a flow against.
43
+ */
44
+ export declare const OAUTH_ROUTE_PATHS: string[];
45
+ /** {@link createOAuthRouteHandlers}, hung on anything with `get` and `post`. */
46
+ export declare const mountOAuthRoutes: (router: RouterLike<OAuthRouteHandler>, options: OAuthRouteHandlersOptions) => void;
@@ -0,0 +1,159 @@
1
+ import { authorizationServerMetadata, protectedResourceMetadata } from "../oauth/metadata.js";
2
+ import { sendErrorJson } from "../oauth/respond.js";
3
+ import { handleAuthorizeStart, handleAuthorizeSubmit } from "../oauth/authorize.js";
4
+ import { handleRegister } from "../oauth/register.js";
5
+ import { handleRevoke, handleToken } from "../oauth/token.js";
6
+ //#region src/core/handlers/oauthRouteHandlers.ts
7
+ /**
8
+ * The host's response, described the way the protocol handlers expect it.
9
+ *
10
+ * They were written against the render pipeline's helpers, which is a different shape from a router's response
11
+ * and deliberately so: one collects a status and headers to be applied later, the other writes as it goes. This
12
+ * is the whole of the difference, in one place, so neither side learns about the other.
13
+ */
14
+ var asHelpers = (res) => {
15
+ const helpers = {
16
+ status: 200,
17
+ headers: {},
18
+ setHeader: (name, value) => {
19
+ helpers.headers[name] = value;
20
+ res.setHeader(name, value);
21
+ },
22
+ setStatus: (code) => {
23
+ helpers.status = code;
24
+ res.status(code);
25
+ },
26
+ send: (body) => {
27
+ res.send(typeof body === "string" ? body : body.toString("utf8"));
28
+ },
29
+ write: (chunk) => {
30
+ res.send(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
31
+ },
32
+ end: () => {
33
+ res.end();
34
+ }
35
+ };
36
+ return helpers;
37
+ };
38
+ /**
39
+ * The request, as the protocol reads it.
40
+ *
41
+ * `protocol` earns its place: the issuer published in the discovery documents is built from it, and a request
42
+ * that omits it publishes `undefined://api.example.com` — a document every client rejects. Behind a proxy the
43
+ * forwarded header is the only thing that knows, since the hop to this process is plain HTTP.
44
+ */
45
+ var asRequest = (req) => {
46
+ const forwarded = req.headers["x-forwarded-proto"];
47
+ const protocol = (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(",")[0] ?? req.protocol ?? "https";
48
+ return {
49
+ method: req.method ?? "GET",
50
+ path: req.path,
51
+ headers: req.headers,
52
+ hostname: req.hostname,
53
+ protocol: protocol === "http" ? "http" : "https",
54
+ query: req.query ?? {}
55
+ };
56
+ };
57
+ /** Query first, body second: a field posted by the grant form wins over one the client left in the URL. */
58
+ var params = (req) => ({
59
+ ...req.query ?? {},
60
+ ...req.body ?? {}
61
+ });
62
+ /**
63
+ * OAuth 2.1 authorization as ordinary routes, for a host that runs a router rather than the render pipeline.
64
+ *
65
+ * The same handlers the pipeline stage calls — this is the mounting, not a second implementation. A deployment
66
+ * whose API is an Express app mounts these; one whose MCP server is the SDK's own pipeline mounts the stage. Both
67
+ * grant the credential `config.adapters.issueToken` mints, and THAT is what distinguishes one mounting from
68
+ * another: a space-scoped agent token for a connector, a user session for a native client.
69
+ *
70
+ * `paths` are relative, so the host decides where the set answers. The discovery documents must be reachable at
71
+ * the well-known paths off the ORIGIN root, so a host that mounts this under a prefix serves those two itself.
72
+ */
73
+ var createOAuthRouteHandlers = ({ config, onError }) => {
74
+ const guarded = (method, path, run) => async (req, res) => {
75
+ try {
76
+ await run(req, res);
77
+ } catch (error) {
78
+ if (onError) onError(error, {
79
+ method,
80
+ path
81
+ });
82
+ else console.error(`[oauth] ${method} ${path} failed:`, error);
83
+ sendErrorJson(asHelpers(res), 500, "server_error", "The authorization server failed to answer.");
84
+ }
85
+ };
86
+ return [
87
+ {
88
+ method: "GET",
89
+ path: "/authorize",
90
+ handle: guarded("GET", "/authorize", async (req, res) => handleAuthorizeStart(config, asHelpers(res), params(req), asRequest(req)))
91
+ },
92
+ {
93
+ method: "POST",
94
+ path: "/authorize",
95
+ handle: guarded("POST", "/authorize", async (req, res) => handleAuthorizeSubmit(config, asHelpers(res), params(req), asRequest(req)))
96
+ },
97
+ {
98
+ method: "POST",
99
+ path: "/token",
100
+ handle: guarded("POST", "/token", async (req, res) => handleToken(config, asHelpers(res), params(req)))
101
+ },
102
+ {
103
+ method: "POST",
104
+ path: "/revoke",
105
+ handle: guarded("POST", "/revoke", async (req, res) => handleRevoke(config, asHelpers(res), params(req)))
106
+ },
107
+ {
108
+ method: "POST",
109
+ path: "/register",
110
+ handle: guarded("POST", "/register", async (req, res) => handleRegister(config, asHelpers(res), req.body))
111
+ },
112
+ {
113
+ method: "GET",
114
+ path: "/.well-known/oauth-authorization-server",
115
+ handle: guarded("GET", "/.well-known/oauth-authorization-server", (req, res) => {
116
+ const helpers = asHelpers(res);
117
+ helpers.setHeader("Content-Type", "application/json");
118
+ helpers.setStatus(200);
119
+ helpers.send(JSON.stringify(authorizationServerMetadata(config, asRequest(req))));
120
+ return Promise.resolve();
121
+ })
122
+ },
123
+ {
124
+ method: "GET",
125
+ path: "/.well-known/oauth-protected-resource",
126
+ handle: guarded("GET", "/.well-known/oauth-protected-resource", (req, res) => {
127
+ const helpers = asHelpers(res);
128
+ helpers.setHeader("Content-Type", "application/json");
129
+ helpers.setStatus(200);
130
+ helpers.send(JSON.stringify(protectedResourceMetadata(config, asRequest(req))));
131
+ return Promise.resolve();
132
+ })
133
+ }
134
+ ];
135
+ };
136
+ /**
137
+ * Where the set answers, relative to wherever it is mounted.
138
+ *
139
+ * Exported so a host's auth policy can DERIVE which paths are public rather than restating them: every one of
140
+ * these is reached without a session by definition — `/authorize` is what a signed-out person opens, `/token` is
141
+ * authenticated by PKCE, and the discovery documents are what a client reads before it has anything at all. A
142
+ * restated list is one that drifts, and drifting the wrong way here means an authorization server nobody can
143
+ * start a flow against.
144
+ */
145
+ var OAUTH_ROUTE_PATHS = [
146
+ "/authorize",
147
+ "/token",
148
+ "/revoke",
149
+ "/register",
150
+ "/.well-known/oauth-authorization-server",
151
+ "/.well-known/oauth-protected-resource"
152
+ ];
153
+ /** {@link createOAuthRouteHandlers}, hung on anything with `get` and `post`. */
154
+ var mountOAuthRoutes = (router, options) => {
155
+ for (const { method, path, handle } of createOAuthRouteHandlers(options)) if (method === "GET") router.get(path, handle);
156
+ else router.post(path, handle);
157
+ };
158
+ //#endregion
159
+ export { OAUTH_ROUTE_PATHS, createOAuthRouteHandlers, mountOAuthRoutes };
@@ -0,0 +1,40 @@
1
+ import { HttpRoute, RouterLike, SocialRouteHandler } from './types';
2
+ import { SocialAuth } from '../auth/oauth';
3
+ import { SessionCookies } from '../auth/session';
4
+ import { SSRSession } from '@plitzi/sdk-shared';
5
+ export interface SocialRouteHandlersOptions {
6
+ /** A `createSocialAuth(...)` result — the providers this deployment has credentials for. */
7
+ social: SocialAuth;
8
+ /** Writes the session at the end of a successful callback, and holds the flow state between the two legs. */
9
+ cookies: SessionCookies;
10
+ /**
11
+ * Mints the session for the account that just signed in. `auth.api.issueSession` is the one to pass: the flow
12
+ * proves WHO, and what a session is worth is the kernel's answer, not the provider's.
13
+ */
14
+ issueSession: (userId: number) => Promise<SSRSession>;
15
+ /** Reports a callback that threw. Without it the failure goes to `console.error`; the caller still gets a 500. */
16
+ onError?: (error: unknown, context: {
17
+ method: string;
18
+ path: string;
19
+ }) => void;
20
+ }
21
+ /**
22
+ * Social sign-in as request handlers: the provider listing, and the two legs of the authorization-code flow.
23
+ *
24
+ * The counterpart to {@link createAuthRouteHandlers}, and it exists for the same reason. `createSocialAuth` already
25
+ * answers the whole flow — where to send the browser, what state to hold, whether the caller came back with anything
26
+ * worth a session — and every deployment then wrote the identical fifty lines that turn those answers into
27
+ * redirects: set the flow cookie, clear it on the way back, decide that a failure with no vetted target has nowhere
28
+ * to report to, write the session cookies before the final redirect. None of that is a deployment's own policy, and
29
+ * two of the steps are the kind that look right while being wrong (clearing the flow cookie only on success leaves a
30
+ * stale nonce; redirecting to an unvetted target is an open redirect).
31
+ *
32
+ * What stays the deployment's is what it already was: which providers it has credentials for, and what
33
+ * `linkAccount` does with the person who comes back.
34
+ *
35
+ * A list, so nothing has to be a router — {@link mountSocialAuthRoutes} is the loop that hangs it on one. Mount it
36
+ * on the same base as the `/auth` flows: the paths are `/providers` and `/<provider>/{login,callback}`.
37
+ */
38
+ export declare const createSocialAuthRouteHandlers: ({ social, cookies, issueSession, onError }: SocialRouteHandlersOptions) => HttpRoute<SocialRouteHandler>[];
39
+ /** {@link createSocialAuthRouteHandlers}, hung on a router. Mount it wherever the `/auth` flows are mounted. */
40
+ export declare const mountSocialAuthRoutes: (router: RouterLike<SocialRouteHandler>, options: SocialRouteHandlersOptions) => void;
@@ -0,0 +1,129 @@
1
+ //#region src/core/handlers/socialRouteHandlers.ts
2
+ /**
3
+ * A provider id from the request.
4
+ *
5
+ * `params` when the host filled it in (Express does, from the `:provider` segment) and the path otherwise, so a
6
+ * framework that does not populate params — or a bare `node:http` dispatch — needs nothing extra. The path form
7
+ * reads the segment before the trailing action, which is where both routes carry it.
8
+ */
9
+ var providerOf = (req) => {
10
+ const fromParams = req.params?.provider;
11
+ if (fromParams) return fromParams;
12
+ const segments = req.path.split("/").filter(Boolean);
13
+ return segments.length >= 2 ? segments[segments.length - 2] : "";
14
+ };
15
+ /**
16
+ * Social sign-in as request handlers: the provider listing, and the two legs of the authorization-code flow.
17
+ *
18
+ * The counterpart to {@link createAuthRouteHandlers}, and it exists for the same reason. `createSocialAuth` already
19
+ * answers the whole flow — where to send the browser, what state to hold, whether the caller came back with anything
20
+ * worth a session — and every deployment then wrote the identical fifty lines that turn those answers into
21
+ * redirects: set the flow cookie, clear it on the way back, decide that a failure with no vetted target has nowhere
22
+ * to report to, write the session cookies before the final redirect. None of that is a deployment's own policy, and
23
+ * two of the steps are the kind that look right while being wrong (clearing the flow cookie only on success leaves a
24
+ * stale nonce; redirecting to an unvetted target is an open redirect).
25
+ *
26
+ * What stays the deployment's is what it already was: which providers it has credentials for, and what
27
+ * `linkAccount` does with the person who comes back.
28
+ *
29
+ * A list, so nothing has to be a router — {@link mountSocialAuthRoutes} is the loop that hangs it on one. Mount it
30
+ * on the same base as the `/auth` flows: the paths are `/providers` and `/<provider>/{login,callback}`.
31
+ */
32
+ var createSocialAuthRouteHandlers = ({ social, cookies, issueSession, onError }) => {
33
+ const report = (error, context) => {
34
+ if (onError) {
35
+ onError(error, context);
36
+ return;
37
+ }
38
+ console.error(`[auth] ${context.method} ${context.path} failed:`, error);
39
+ };
40
+ return [
41
+ {
42
+ method: "GET",
43
+ path: "/providers",
44
+ handle: (_req, res) => {
45
+ res.status(200).json({ providers: social.list() });
46
+ return Promise.resolve();
47
+ }
48
+ },
49
+ {
50
+ /**
51
+ * Where a sign-in screen sends somebody once they have a session.
52
+ *
53
+ * It exists because the screen cannot make this decision itself. The destination arrives as `?redirect=` on a
54
+ * link the visitor followed, so it is attacker-supplied — and a shared sign-in page that navigates to it
55
+ * unchecked is an open redirect on the one origin every person on the platform is trained to type a password
56
+ * into. The page is also a rendered space, with no way to ask whether an origin is ours.
57
+ *
58
+ * So the page ends its flow HERE and this hop decides, with the same `sanitizeRedirect` the social callback
59
+ * uses: a relative path or an allowed origin is honoured, anything else silently becomes the default. A
60
+ * missing or unresolved parameter lands on the default too, which is what makes it safe for a template to
61
+ * pass a token it could not fill in.
62
+ */
63
+ method: "GET",
64
+ path: "/continue",
65
+ handle: (req, res) => {
66
+ res.redirect(social.sanitizeRedirect(req.query?.redirect));
67
+ return Promise.resolve();
68
+ }
69
+ },
70
+ {
71
+ method: "GET",
72
+ path: "/:provider/login",
73
+ handle: (req, res) => {
74
+ const started = social.start(providerOf(req), req.query?.redirect);
75
+ if (!started) {
76
+ res.status(404).json({ error: "Unknown provider" });
77
+ return Promise.resolve();
78
+ }
79
+ cookies.writeFlow(req, res, started.stateCookie, started.ttl);
80
+ res.redirect(started.redirectTo);
81
+ return Promise.resolve();
82
+ }
83
+ },
84
+ {
85
+ method: "GET",
86
+ path: "/:provider/callback",
87
+ handle: async (req, res) => {
88
+ const providerId = providerOf(req);
89
+ try {
90
+ const { code, state, error } = req.query ?? {};
91
+ const stateCookie = cookies.readFlow(req);
92
+ cookies.clearFlow(req, res);
93
+ const result = await social.complete(providerId, {
94
+ code,
95
+ state,
96
+ error,
97
+ stateCookie
98
+ });
99
+ if (!result.ok) {
100
+ if (!result.redirectTo) {
101
+ res.status(400).json({ error: result.error });
102
+ return;
103
+ }
104
+ if (result.reason === "server_error") report(new Error(result.error), {
105
+ method: "GET",
106
+ path: `/${providerId}/callback`
107
+ });
108
+ res.redirect(result.redirectTo);
109
+ return;
110
+ }
111
+ cookies.write(req, res, await issueSession(result.account.id));
112
+ res.redirect(result.redirectTo);
113
+ } catch (error) {
114
+ report(error, {
115
+ method: "GET",
116
+ path: `/${providerId}/callback`
117
+ });
118
+ res.status(500).json({ error: "Internal server error" });
119
+ }
120
+ }
121
+ }
122
+ ];
123
+ };
124
+ /** {@link createSocialAuthRouteHandlers}, hung on a router. Mount it wherever the `/auth` flows are mounted. */
125
+ var mountSocialAuthRoutes = (router, options) => {
126
+ for (const route of createSocialAuthRouteHandlers(options)) router.get(route.path, route.handle);
127
+ };
128
+ //#endregion
129
+ export { createSocialAuthRouteHandlers, mountSocialAuthRoutes };
@@ -0,0 +1 @@
1
+ export {};
@@ -11,6 +11,13 @@ import { CookieSink } from '../auth/session';
11
11
  export interface AuthedRequest extends CredentialCarrier {
12
12
  /** The path alone, without the query string — what the policy matches on. */
13
13
  path: string;
14
+ /** Path parameters, when the host router fills them in. Only the social flows read one, and they fall back to
15
+ * the path when it is absent, so a framework that does not populate this needs nothing extra. */
16
+ params?: Record<string, string | undefined>;
17
+ /** Read by the CSRF check, which asks nothing of a safe method. Absent is treated as `GET`. */
18
+ method?: string;
19
+ /** `http` or `https`, when the host router knows. The OAuth flows publish an issuer built from it. */
20
+ protocol?: string;
14
21
  /** The parsed body, when there is one. The flows read fields off it; how it got parsed is the host's business. */
15
22
  body?: unknown;
16
23
  /** The account this request proved, once the auth middleware has run. */
@@ -23,15 +30,25 @@ export interface JsonResponse extends CookieSink {
23
30
  json: (body: unknown) => unknown;
24
31
  }
25
32
  export type RouteHandler = (req: AuthedRequest, res: JsonResponse) => Promise<void>;
33
+ /**
34
+ * A response that can also send the browser somewhere.
35
+ *
36
+ * Only the social flows need it: an authorization-code grant ends in a redirect, not a body. Express satisfies it
37
+ * as it stands; a bare `node:http` response needs three lines (302 plus a `Location` header).
38
+ */
39
+ export interface RedirectResponse extends JsonResponse {
40
+ redirect: (url: string) => unknown;
41
+ }
42
+ export type SocialRouteHandler = (req: AuthedRequest, res: RedirectResponse) => Promise<void>;
26
43
  /** One flow, ready to serve: where it answers, and what to call when it does. */
27
- export interface HttpRoute {
44
+ export interface HttpRoute<H = RouteHandler> {
28
45
  method: 'GET' | 'POST';
29
46
  /** Relative to wherever the host mounts the set — `/auth`, conventionally. */
30
47
  path: string;
31
- handle: RouteHandler;
48
+ handle: H;
32
49
  }
33
50
  /** Anything routes can be hung on: a router, an app, or a stand-in in a test — `get` and `post`, nothing else. */
34
- export interface RouterLike {
35
- get: (path: string, handler: RouteHandler) => unknown;
36
- post: (path: string, handler: RouteHandler) => unknown;
51
+ export interface RouterLike<H = RouteHandler> {
52
+ get: (path: string, handler: H) => unknown;
53
+ post: (path: string, handler: H) => unknown;
37
54
  }
@@ -7,4 +7,14 @@ export interface HealthIdentity {
7
7
  version?: string;
8
8
  }
9
9
  export declare const buildHealthPayload: (identity?: Partial<HealthIdentity>) => Record<string, unknown>;
10
- export declare const registerHealthCheck: (app: HealthCheckApp, identity: HealthIdentity) => void;
10
+ /**
11
+ * Attach the generic /health endpoint to an Express-style app — a deployment whose roles are not all on the sdk-server
12
+ * http pipeline. Servers built on the pipeline get the same endpoint from `healthStage`.
13
+ *
14
+ * `check` is what makes it a readiness probe rather than a liveness one, and it is here for the same reason it is on
15
+ * `healthStage`: a role reports the stores it depends on, and `healthy: false` answers 503 so an orchestrator stops
16
+ * routing to a replica that can serve nothing. Without it the two halves of one deployment answered different
17
+ * questions at the same path — and the Express half's real check ended up as a second, password-guarded endpoint the
18
+ * probes were never pointed at.
19
+ */
20
+ export declare const registerHealthCheck: (app: HealthCheckApp, identity: HealthIdentity, check?: () => Record<string, unknown> | Promise<Record<string, unknown>>) => void;
@@ -4,11 +4,35 @@ var buildHealthPayload = (identity = {}) => ({
4
4
  Version: identity.version ? `v${identity.version}` : "unknown",
5
5
  role: identity.role
6
6
  });
7
- var registerHealthCheck = (app, identity) => {
7
+ /**
8
+ * Attach the generic /health endpoint to an Express-style app — a deployment whose roles are not all on the sdk-server
9
+ * http pipeline. Servers built on the pipeline get the same endpoint from `healthStage`.
10
+ *
11
+ * `check` is what makes it a readiness probe rather than a liveness one, and it is here for the same reason it is on
12
+ * `healthStage`: a role reports the stores it depends on, and `healthy: false` answers 503 so an orchestrator stops
13
+ * routing to a replica that can serve nothing. Without it the two halves of one deployment answered different
14
+ * questions at the same path — and the Express half's real check ended up as a second, password-guarded endpoint the
15
+ * probes were never pointed at.
16
+ */
17
+ var registerHealthCheck = (app, identity, check) => {
8
18
  const payload = buildHealthPayload(identity);
9
19
  app.get("/health", (_req, res) => {
10
- res.writeHead(200, { "Content-Type": "application/json" });
11
- res.end(JSON.stringify(payload));
20
+ (async () => {
21
+ let live = {};
22
+ if (check) try {
23
+ live = await check();
24
+ } catch (error) {
25
+ live = {
26
+ healthy: false,
27
+ reason: error instanceof Error ? error.message : String(error)
28
+ };
29
+ }
30
+ res.writeHead(live.healthy === false ? 503 : 200, { "Content-Type": "application/json" });
31
+ res.end(JSON.stringify({
32
+ ...payload,
33
+ ...live
34
+ }));
35
+ })();
12
36
  });
13
37
  };
14
38
  //#endregion
@@ -3,5 +3,5 @@ import { RawResponse } from '../../helpers/buildResponseHelpers';
3
3
  import { Handler } from '../transports';
4
4
  import { SSRRequest, SSRResponseHelpers, SSRServerConfig } from '@plitzi/sdk-shared';
5
5
  import { IncomingMessage } from 'node:http';
6
- export type BuildContext<C extends BaseContext> = (raw: IncomingMessage, rawRes: RawResponse, req: SSRRequest, res: SSRResponseHelpers) => C;
6
+ export type BuildContext<C extends BaseContext> = (raw: IncomingMessage, rawRes: RawResponse, req: SSRRequest, res: SSRResponseHelpers) => Omit<C, 'signal'>;
7
7
  export declare const makeHandler: <C extends BaseContext>(label: string, buildContext: BuildContext<C>, stages: Stage<C>[], compressionConfig?: SSRServerConfig["compression"]) => Handler;
@@ -15,7 +15,26 @@ var runPipeline = async (raw, rawRes, buildContext, stages, server, compression)
15
15
  const startedAt = Date.now();
16
16
  const req = parseRequest(raw);
17
17
  const res = buildResponseHelpers(rawRes, req.headers["accept-encoding"], compression);
18
- const ctx = buildContext(raw, rawRes, req, res);
18
+ /**
19
+ * One controller per request, aborted when the PEER goes away.
20
+ *
21
+ * Watched on the RESPONSE, not on the request: `IncomingMessage` emits `close` as soon as its body has been
22
+ * read, so a POST aborted itself the millisecond the server finished parsing it — measurably, at 9ms of a
23
+ * 300ms request. Everything downstream either ignored that (a listener attached after the event never fires,
24
+ * which is what kept actions working at all) or acted on it and cancelled work nobody had abandoned.
25
+ *
26
+ * `writableFinished` is what tells the two apart: a response that closed after finishing was SERVED, and one
27
+ * that closed before is a caller who left. `once` so a request that ends normally leaves no listener on a
28
+ * socket the runtime may keep alive for the next one.
29
+ */
30
+ const controller = new AbortController();
31
+ rawRes.once?.("close", () => {
32
+ if (!rawRes.writableFinished) controller.abort();
33
+ });
34
+ const ctx = {
35
+ ...buildContext(raw, rawRes, req, res),
36
+ signal: controller.signal
37
+ };
19
38
  const logger = ctx.config.logger;
20
39
  const ip = logger ? clientIp(raw, req) : "";
21
40
  const logRequest = (error) => {
@@ -49,7 +49,7 @@ var createAuthApiStage = (auth, basePath = auth.basePath) => {
49
49
  actor: decision.actor
50
50
  };
51
51
  const outcome = await route.handler(authRequest);
52
- auth.applySession(req, res, outcome, auth.cookies);
52
+ auth.applySession(req, res, outcome);
53
53
  if (isNavigation(req)) {
54
54
  res.setStatus(303);
55
55
  res.setHeader("Location", outcome.ok ? safeRedirectTarget(req) : req.path);
@@ -0,0 +1,9 @@
1
+ import { Stage } from '../types';
2
+ /**
3
+ * The font files this deployment stores, when it stores them itself.
4
+ *
5
+ * Off unless `fonts.dir` is configured, because a cloud deployment serves them from a CDN and would otherwise
6
+ * shadow that path with a directory it does not have. Served CORS-open on purpose: a font is fetched in CORS mode
7
+ * whatever its origin, so without this a page on the space's own domain cannot use a face from this server.
8
+ */
9
+ export declare const fontAssetsStage: Stage;
@@ -0,0 +1,27 @@
1
+ import { serveStatic } from "../../staticFiles.js";
2
+ //#region src/core/http/stages/fontAssets.ts
3
+ /** Where a manifest's `hosted` paths are addressed from by default — see `fontUrlResolver`. */
4
+ var FONTS_PREFIX = "/fonts";
5
+ /**
6
+ * The font files this deployment stores, when it stores them itself.
7
+ *
8
+ * Off unless `fonts.dir` is configured, because a cloud deployment serves them from a CDN and would otherwise
9
+ * shadow that path with a directory it does not have. Served CORS-open on purpose: a font is fetched in CORS mode
10
+ * whatever its origin, so without this a page on the space's own domain cannot use a face from this server.
11
+ */
12
+ var fontAssetsStage = (ctx) => {
13
+ const dir = ctx.config.fonts?.dir;
14
+ if (!dir || !ctx.req.path.startsWith(`${FONTS_PREFIX}/`)) return false;
15
+ ctx.res.setHeader("Access-Control-Allow-Origin", "*");
16
+ ctx.res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
17
+ const strippedReq = {
18
+ ...ctx.req,
19
+ path: ctx.req.path.slice(6) || "/"
20
+ };
21
+ if (serveStatic(strippedReq, ctx.res, dir)) return true;
22
+ ctx.res.setStatus(404);
23
+ ctx.res.send("Not found");
24
+ return true;
25
+ };
26
+ //#endregion
27
+ export { fontAssetsStage };
@@ -0,0 +1,19 @@
1
+ import { Stage } from '../types';
2
+ import { OAuthConfig } from '@plitzi/sdk-shared';
3
+ /** OAuth 2.1 authorization for the MCP server, mounted ONLY when a deployment configures `oauth`. Without it the
4
+ * stage falls straight through and the server keeps its anonymous contract: discovery 404s, the public surface
5
+ * (handshake, listings, the guide, plitzi_render) answers without a token, and nothing below changes.
6
+ *
7
+ * It sits before the MCP stage because that one answers every path on a dedicated MCP server — these endpoints
8
+ * would otherwise be swallowed by the JSON-RPC transport, which is exactly the 406 a host hits on /register. */
9
+ export declare const createOAuthStage: (oauth?: OAuthConfig) => Stage;
10
+ /** The protected-resource half of OAuth: an MCP call that presents no bearer this server can verify is refused
11
+ * with RFC 6750's challenge instead of being served the anonymous surface. That 401 is the whole handshake — it
12
+ * is how a host learns the server needs authorization, where its metadata lives and which scopes to ask for, and
13
+ * a 200 tells it none of that. Only the JSON-RPC POST is guarded: the CORS preflight, the GET 405 and the
14
+ * discovery probes carry no credential and must keep answering as they do.
15
+ *
16
+ * Mounted only when `oauth` is configured. A deployment that configures none keeps the open server it had, where
17
+ * the whole public surface — handshake, listings, the guide, plitzi_render — answers without a token; with OAuth
18
+ * on, the grant that carries no space is what covers that same ground. */
19
+ export declare const createOAuthGuardStage: (oauth?: OAuthConfig) => Stage;