mirror of
https://github.com/suitenumerique/docs.git
synced 2026-09-03 01:08:47 +02:00
🔒️(collaboration) reject admin jwts not issued for the yhub audience
yhub verified Django's RS256 admin JWT without checking "aud", so the y-converter token Django hands to the converter process was replayable here — and admin: true short-circuits getAccessType to "rw" on every document, plus the backend-internal reset-connections purpose and the X-User-Id attribution override. Require aud: "yhub", as y-provider already does for its own audience. Nothing in the backend calls yhub's admin endpoints yet, so no caller is affected. Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
This commit is contained in:
+6
-3
@@ -223,12 +223,15 @@ and this project adheres to
|
||||
so the Django backend can create documents without speaking yhub's lib0
|
||||
wire encoding. Strict create (409 when the document already has content),
|
||||
initial content attributed to the optional `X-User-Id` header; guarded by
|
||||
standard document write access (admin JWT or user session)
|
||||
standard document write access (the `aud: "yhub"` admin JWT, or a user
|
||||
session with update ability)
|
||||
- ✨(collaboration) add an admin reset-connections endpoint on yhub:
|
||||
`POST /collaboration/reset-connections/v1/docs/{id}` re-checks the
|
||||
authorization of the document's connected clients and disconnects (close
|
||||
code 4401) only those whose access changed. Authenticated with the admin
|
||||
JWT verified against the backend JWKS; not yet triggered by the backend on
|
||||
code 4401) only those whose access changed. Authenticated with an admin JWT
|
||||
verified against the backend JWKS and required to carry `aud: "yhub"`, so
|
||||
an admin token Django issued for another service (e.g. the `y-converter`
|
||||
one) cannot be replayed here; not yet triggered by the backend on
|
||||
permission changes (follow-up)
|
||||
- ⬆️(collaboration) upgrade yhub to 0.4.0 and serve all its routes under the
|
||||
`/collaboration/` prefix (`server.apiPrefix`): the websocket moves to
|
||||
|
||||
@@ -22,6 +22,12 @@ const allowedOrigins = (
|
||||
).split(',');
|
||||
const Y_PROVIDER_API_KEY = secret('Y_PROVIDER_API_KEY', 'yprovider-api-key');
|
||||
const ORG = process.env.YHUB_ORG || 'docs';
|
||||
// Requiring this audience stops a valid admin JWT that Django issued for
|
||||
// another service (today: the y-converter token in converter_services.py,
|
||||
// which is handed to the converter process) from being replayed against yhub.
|
||||
// Hardcoded, like y-provider's Y_CONVERTER_AUDIENCE: both ends of a two-party
|
||||
// contract, so an env var would only add a way to misconfigure it into a 401.
|
||||
const YHUB_AUDIENCE = 'yhub';
|
||||
// lowercase only (no /i): Django serializes UUIDs lowercase, while yhub rooms
|
||||
// and S3 keys are case-sensitive strings — accepting case variants would let a
|
||||
// client open a parallel room for the same document (and, with soft migration,
|
||||
@@ -407,6 +413,7 @@ const auth = createAuthPlugin({
|
||||
// silently dropped as a 401.
|
||||
const { payload } = await jwtVerify(token, JWKS, {
|
||||
algorithms: ['RS256'],
|
||||
audience: YHUB_AUDIENCE,
|
||||
clockTolerance: 5,
|
||||
});
|
||||
// admin tokens act as the "system" user (no per-user admin identities yet)
|
||||
@@ -414,7 +421,9 @@ const auth = createAuthPlugin({
|
||||
? { userid: 'system', admin: true }
|
||||
: null;
|
||||
} catch {
|
||||
return null; // bad signature / expired / JWKS unreachable — fail closed
|
||||
// bad signature / expired / wrong (or missing) audience / JWKS
|
||||
// unreachable — fail closed
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (gcOff) return null; // full-history connections: not for Docs users
|
||||
|
||||
Reference in New Issue
Block a user