feat(server-mode): share one session; scope collaborators' pane tokens (sharing phase 3)

A workspace's owner (or an admin) can share ONE session from its right-click
menu (GET/PUT /api/lanes/:id/sessions/:sid/shares, table session_shares,
audited). The user sees the workspace (access: "session") with only that
session, joins the pane holding it, resumes it on their own account in a
free slot, and uses the workspace's git and Files reads and git writes;
everything else of the workspace answers 403 ESESSIONSHARE (scope.js
sessionShareAllows in scopeGuard). Hook/runtime broadcasts stay with full
collaborators.

A collaborator's pane token (readable by the owner's Linux account) now acts
as them inside that workspace only, and never starts or bills a session
(auth.js paneLaneAllows, 403 EPANESCOPE); a Terminal tab the owner or an
admin opens acts as the owner again. Docs say plainly that under S3 a
session share hands over the owner's account inside the terminal.
This commit is contained in:
2026-11-06 11:25:40 +07:00
parent 1a365a45bd
commit 879a6bb02d
24 changed files with 838 additions and 120 deletions
+43
View File
@@ -738,6 +738,49 @@ const paths = {
},
},
},
"/api/lanes/{id}/sessions/{sid}/shares": {
get: {
tags: ["Lanes"],
summary: "The users one session of a workspace is shared with",
description:
"Server mode (local mode: `{shares: [], canManage: false}`). `{shares:[{id, username, name, sharedAt}], canManage}` for session `sid` of this workspace (`404 ENOSESSION` otherwise).",
operationId: "getSessionShares",
parameters: [
idParam,
{ name: "sid", in: "path", required: true, schema: { type: "string" } },
],
responses: { 200: { description: "`{shares, canManage}`.", ...json(obj) }, 404: notFound },
},
put: {
tags: ["Lanes"],
summary: "Set the users one session of a workspace is shared with",
description:
"`{userIds:[…]}` is the WHOLE list (≤ 200 enabled users, never the workspace's owner). The owner or an admin only (`403 ENOTOWNER`); same-origin; audited as `share session <id>` / `unshare session <id>`. A user it is shared with sees that session and its workspace (`access: \"session\"` in the workspace list), joins the pane holding it, resumes it on their own account and uses the workspace's git — every other call on the workspace answers them `403 ESESSIONSHARE`.",
operationId: "setSessionShares",
parameters: [
idParam,
{ name: "sid", in: "path", required: true, schema: { type: "string" } },
],
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
required: ["userIds"],
properties: { userIds: { type: "array", items: { type: "integer" } } },
},
},
},
},
responses: {
200: { description: "`{shares}`.", ...json(obj) },
400: { description: "EBADUSER or ELOCALMODE." },
403: { description: "ENOTOWNER." },
404: notFound,
},
},
},
"/api/lanes/{id}/memory": {
get: {
tags: ["Lanes"],