From 81a775ec3408d89b95acb89377b06994536c6bc5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 20:56:49 +0000 Subject: [PATCH 1/2] Document management endpoint URL form and virtual host policy for Universal APIs Universal APIs mounted on the management endpoint are served at the root of the port, not under /api/, and there is no API index there. Adds a section on honouring the per-API verb policy a management virtual host delivers through context attributes. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Nfw59S52x9S9Mo18FSiZ7T --- docs/web/apis.adoc | 39 ++++++++++++++++++++++++++++++++++---- docs/web/images/xp-820.svg | 1 + 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 docs/web/images/xp-820.svg diff --git a/docs/web/apis.adoc b/docs/web/apis.adoc index 2261870c..b3f6834f 100644 --- a/docs/web/apis.adoc +++ b/docs/web/apis.adoc @@ -100,9 +100,9 @@ A Universal API can be exposed through three *independent* axes — they don't s . *Service point* — the API is mounted into a `site`, `webapp`, or `admin tool` service via that service's descriptor. . *Web endpoint* — the API opts in with `mount: "web"` and becomes reachable on the *Web* endpoint (the public XP port) under `/api/`. -. *Management endpoint* — the API opts in with `mount: "management"` and becomes reachable on the *Management* endpoint, which listens on its own port. +. *Management endpoint* — the API opts in with `mount: "management"` and becomes reachable on the *Management* endpoint, which listens on its own port, at the root: `+/:+`. -The `/api/` URL prefix is the same on both endpoints, but the two are served on *different ports*. The Management endpoint is not reachable through the Web endpoint and vice versa. +The two endpoints are served on *different ports*, and the URL forms differ: `/api/` prefix on the Web endpoint, no prefix on the Management endpoint. The Management endpoint is not reachable through the Web endpoint and vice versa. [#service-point] == Service point @@ -173,7 +173,7 @@ WARNING: The `mount: "management"` feature is experimental and subject to change NOTE: The Management endpoint is for *advanced use only*. It is intended for extending XP's management capabilities (cluster operations, internal tooling, infrastructure integrations) — not for regular application APIs. -An API can also be exposed on the *Management* endpoint by adding `"management"` to its `mount` list. The Management endpoint is a separate listener on its own port, distinct from the Web endpoint — use it for internal or operational APIs that should not be reachable from the public network. +An API can also be exposed on the *Management* endpoint by adding `"management"` to its `mount` list. The Management endpoint is a separate listener on its own port, distinct from the Web endpoint — use it for internal or operational APIs that should not be reachable from the public network. There the API is served at the root of the port, `+/:+`, beside XP's own <> (`server:app`, `server:snapshot`, ...). Every request must carry an authenticated user; `allow` decides which. .src/main/resources/apis//.yaml [source,yaml] @@ -187,9 +187,40 @@ allow: Both mounts can be combined: `mount: ["web", "management"]` makes the API reachable on both endpoints. The `allow` principal list is enforced on every request regardless of which endpoint served it. +[#management-policy] +=== Honouring virtual host policy + +image:xp-8020.svg[XP 8.2.0,opts=inline] A <> can narrow what each API exposes through it. The mapping declares settings under `+context.api.\:.+` (the colon is escaped because the file is a Java properties file); XP copies them into the request context as attributes named `+api.:.+`. XP's own resources all understand `verbs`, the comma separated list of operations the mapping exposes; an API of your own reads its settings the same way and decides what they mean. An absent setting must mean "unrestricted" - a request that arrives without a matching mapping, or on the default management port, carries none. + +.src/main/resources/apis/cluster/cluster.ts +[source,typescript] +---- +import {get as getContext} from '/lib/xp/context'; + +const KEY = 'com.example.ops:cluster'; + +function allows(verb: string): boolean { + const value = getContext().attributes[`api.${KEY}.verbs`]; + if (typeof value !== 'string' || value.trim() === '') { + return true; + } + const verbs = value.split(',').map((v) => v.trim()); + return verbs.includes('*') || verbs.includes(verb); +} + +export function GET(request: XP.Request): XP.Response { + if (!allows('list')) { + return {status: 403, body: {message: 'Verb [list] is not exposed on this virtual host'}}; + } + return {body: {nodes: listNodes()}}; +} +---- + +With `+mapping.ops.context.api.com.example.ops\:cluster.verbs = list+`, the mapping exposes `list` and nothing else the API guards. + == API discovery -`GET /api` (or `GET /api/`) returns a JSON index of every API mounted on the *endpoint that received the request*. Calling it on the Web endpoint lists APIs with `"web"` in `mount`; calling it on the Management endpoint lists APIs with `"management"` in `mount`. The response is a `resources` array, one entry per API: +`GET /api` (or `GET /api/`) on the Web endpoint returns a JSON index of every API with `"web"` in `mount`. There is no index on the Management endpoint. The response is a `resources` array, one entry per API: [source,json] ---- diff --git a/docs/web/images/xp-820.svg b/docs/web/images/xp-820.svg new file mode 100644 index 00000000..cf359793 --- /dev/null +++ b/docs/web/images/xp-820.svg @@ -0,0 +1 @@ +XPXP8.2.08.2.0 From 948bb216efca5c9247a354242b54a565b4918289 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 18:33:13 +0000 Subject: [PATCH 2/2] State the vhost policy contract without rationale Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Nfw59S52x9S9Mo18FSiZ7T --- docs/web/apis.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/web/apis.adoc b/docs/web/apis.adoc index b3f6834f..88a02415 100644 --- a/docs/web/apis.adoc +++ b/docs/web/apis.adoc @@ -173,7 +173,7 @@ WARNING: The `mount: "management"` feature is experimental and subject to change NOTE: The Management endpoint is for *advanced use only*. It is intended for extending XP's management capabilities (cluster operations, internal tooling, infrastructure integrations) — not for regular application APIs. -An API can also be exposed on the *Management* endpoint by adding `"management"` to its `mount` list. The Management endpoint is a separate listener on its own port, distinct from the Web endpoint — use it for internal or operational APIs that should not be reachable from the public network. There the API is served at the root of the port, `+/:+`, beside XP's own <> (`server:app`, `server:snapshot`, ...). Every request must carry an authenticated user; `allow` decides which. +An API can also be exposed on the *Management* endpoint by adding `"management"` to its `mount` list. The Management endpoint is a separate listener on its own port, distinct from the Web endpoint — use it for internal or operational APIs that should not be reachable from the public network. There the API is served at the root of the port, `+/:+`, beside XP's own <> (`server:app`, `server:snapshot`, ...). Every request must carry an authenticated user matching `allow`. .src/main/resources/apis//.yaml [source,yaml] @@ -190,7 +190,7 @@ Both mounts can be combined: `mount: ["web", "management"]` makes the API reacha [#management-policy] === Honouring virtual host policy -image:xp-8020.svg[XP 8.2.0,opts=inline] A <> can narrow what each API exposes through it. The mapping declares settings under `+context.api.\:.+` (the colon is escaped because the file is a Java properties file); XP copies them into the request context as attributes named `+api.:.+`. XP's own resources all understand `verbs`, the comma separated list of operations the mapping exposes; an API of your own reads its settings the same way and decides what they mean. An absent setting must mean "unrestricted" - a request that arrives without a matching mapping, or on the default management port, carries none. +image:xp-8020.svg[XP 8.2.0,opts=inline] A <> selects what each API exposes through it. The mapping declares settings under `+context.api.\:.+` (the colon escaped, as in any properties file); XP copies them into the request context as attributes named `+api.:.+`. XP's own resources understand `verbs`, the comma separated list of operations the mapping exposes. An API of your own reads its settings from the same attributes and defines their meaning. An absent setting means unrestricted: a request without a matching mapping, or on the default management port, carries none. .src/main/resources/apis/cluster/cluster.ts [source,typescript]