Files
OpenMAIC/lib/server
AbelWangYaBoandwyuc 05d782090f feat(persistence): resolve single-tenant requests to a shared owner id (#1639)
Course documents, folders, materials and agent sessions are partitioned by an
owner id that comes from a 30-day anonymous cookie, because there is no host
auth layer. For one team behind one ACCESS_CODE that partition buys nothing —
those visitors already share the site password — and it costs: a second browser
shows an empty course list, and POST /api/stages/[id]/publish refuses every
owner, because publishing a cookie partition is not something the product
allows.

PERSISTENCE_SHARED_OWNER_ID resolves every request to that fixed id instead.
Unset — the default — changes nothing.

- **ACCESS_CODE is required.** Without one the middleware lets every request
  through, so a single shared owner would expose one readable, editable and
  publishable course library to anyone who can reach the deployment. That is
  worse than the per-browser partitioning this setting removes, so the
  combination refuses to start, from instrumentation.ts, with a message naming
  both variables. The alternative the issue allowed — ignore it with a warning —
  was rejected because a silently ignored setting is exactly the confusion this
  feature exists to remove.
- The value is validated rather than trusted: 1-128 characters of
  [A-Za-z0-9._-]. That excludes the reserved `anon:` prefix, which would still
  be refused by publish and would alias onto a cookie owner, and any character
  the material-key sanitiser rewrites, which could collide two ids onto one
  object key. A malformed value fails startup on the same path; an empty value
  is treated as unset, since KEY= in an env file cannot be told apart from an
  operator who meant to leave the feature off.
- An explicit authenticatedOwnerId still outranks it, so adding a real auth
  layer later does not require clearing the variable first.
- All four places that derive an owner honour it, including the Server Action
  in lib/workbench/workspace-actions.ts, which re-reads the cookie itself. That
  one is easy to miss and would make the workspace list and its row actions
  disagree about who owns a session.
- The env docs state the trust model next to the variable, cross-reference
  SECURITY.md, and note that this answers who owns a course while DATABASE_URL
  answers where one lives — a second browser sees an empty list on Postgres too.

This is the stopgap asked for in #1550 — a PR in the spirit of the closed
#1551 — with threading a real authenticated owner left as the long-term fix.

AI-assisted commit

Co-authored-by: wyuc <wang-yc24@mails.tsinghua.edu.cn>
2026-09-22 17:33:02 +08:00
..