Hub API
Entrosity Hub's app is a plain client of a REST API under
/api/platform/v1. The full endpoint reference is the
Hub API reference, rendered from
entrosity-hub.backend/api/openapi.yaml, the spec the Hub's router and request
validation are generated from.
Conventions
- JSON bodies with
snake_casekeys; request schemas refuse unknown fields (422). Errors are problem documents with acode(Error codes). - Session:
POST /auth/loginsets the session cookieplatform_rt(HttpOnly,SameSite=Strict, path/api/platform/v1/auth, 30 days, rotated on every refresh with reuse detection) and returns a 15-minute access token for the Hub's own API (Authorization: Bearer, audienceplatform). - Cookie routes (
/auth/refresh,/auth/logout,/auth/product-token,/auth/step-up) accept only requests from the Hub's own origin: a browser'sSec-Fetch-Sitemust besame-origin, or itsOriginthe Hub's (PLATFORM_PUBLIC_URL). Sibling hosts of the same site are refused. - Access: every route has a rule in
entrosity-hub.backend/internal/http/api/access.go: public, session cookie, signed in, organization admin (organization routes answer 404 to anyone who is not an admin of that organization or a platform admin), or platform admin (403 otherwise). - Paginated lists take
?page=&page_size=and returnitems,page,page_sizeandtotal.
Tokens for products
Products never see the session cookie. Their web apps, served on the same host, ask the Hub for short tokens with it:
| Call | Token | Lifetime |
|---|---|---|
POST /auth/product-token {product} | Product access token: aud = the product id (rmm), sub = user, sid = session, email, name. No roles: products read them from their copy of the Hub's data. 403 no_product_access without a role in the product, 403 totp_setup_required when an organization requires two-factor authentication and it is not set up. | 5 minutes |
POST /auth/step-up {product, password} | Step-up token for one sensitive action (purpose: step-up, single-use jti, bound to user and session). A wrong password is a 422 on password; rate-limited like sign-in. | 2 minutes |
Both calls are read-only for the session (they never rotate the cookie), so
they can run while another tab refreshes. Tokens are Ed25519 JWTs
(alg: EdDSA) with issuer PLATFORM_PUBLIC_URL and a kid header; the
public keys are published at GET /api/platform/v1/.well-known/jwks.json
and on the internal listener.
Internal API
A second listener (PLATFORM_INTERNAL_ADDR) serves products over the
private network; Caddy never routes to it.
| Route | Authentication | Returns |
|---|---|---|
GET /internal/v1/jwks.json | none | The signing keys (current and, during a rotation, the previous one). |
GET /internal/v1/products/{product}/snapshot | Authorization: Bearer <product token> from PLATFORM_PRODUCT_TOKENS (constant-time compare) | Everything the product needs to authorize: users with a role in the product and platform admins (any status), organizations with the product enabled, the product roles, and sessions ended in the last 15 minutes. ETag / If-None-Match answer 304 when nothing changed. |
Axis pulls the snapshot every RMM_PLATFORM_SYNC_INTERVAL
(Architecture).