39948-vm/documentation/private-production-presentations.md
2026-07-03 16:11:24 +02:00

4.7 KiB

Private Production Presentations

Production presentations are public by default at /p/[slug]. Each project can switch its production presentation to private with the project-level production_presentation_visibility field.

Project Visibility

Storage: projects.production_presentation_visibility

Values:

  • public - default; anonymous users can open /p/[slug]
  • private - anonymous users must log in; access is then checked by staff permissions or customer grants

The project create/edit forms expose this as Production Presentation Visibility. Existing projects stay public after migration for backward compatibility.

Customer Access Grants

Storage: production_presentation_access

This table grants a customer user access to a private production presentation:

  • projectId
  • userId
  • unique active pair: (projectId, userId)

User lifecycle remains in the existing users system. Presentation access is stored in DB rows, not config files.

Runtime Access Flow

Services:

  • backend/src/services/runtime-presentation-access.ts handles slug lookup and private-presentation listing.
  • backend/src/services/access-policy.ts owns the final access decision through canViewProductionPresentation(user, projectSlug).

The service determines:

  • whether the project slug points to a private production presentation
  • whether an authenticated user can use admin APIs (Public users are never treated as staff, even if stale permissions exist)
  • whether a customer user has a production_presentation_access row
  • which private production slugs are allowed for the current customer user

For public production slugs, read-only runtime requests stay public.

For private production slugs:

  • anonymous GET requests return 401
  • authenticated users with any RBAC permission can access all private production presentations as platform staff
  • authenticated users with Public role and no permissions must have an access row for the project
  • authenticated but unauthorized users receive 403
  • authorized users are marked as req.isRuntimePublicRequest = true so runtime routes return only runtime-safe entity fields

Protected runtime data includes:

  • /api/projects
  • /api/tour_pages
  • /api/project_audio_tracks
  • /api/project-transition-settings/project/:projectId/env/production

User Creation And Edit Flow

Administrator, Platform Owner, and Account Manager can create users and edit existing users.

When the selected role is Public, the user form shows Allowed Private Production Presentations. The selector lists only projects whose production presentation visibility is private.

On create submit:

  • the user is created normally
  • if the selected role is Public, selected private project IDs create production_presentation_access rows
  • if the selected role is not Public, selected project IDs are ignored

On edit submit:

  • existing production_presentation_access rows for that user are replaced by the selected private project IDs
  • switching a user away from Public clears their private presentation grants
  • loading a Public user edit form preselects the user's current private presentation grants

Do not grant customer viewer users broad app permissions such as READ_PROJECTS or READ_TOUR_PAGES; any RBAC permission makes the user platform staff for private presentation access.

Backend hardening also rejects custom permissions for users whose role is Public, rejects permissions assigned to the Public role through the Roles service, and ignores Public user or role-fallback permissions in AccessPolicy for admin APIs.

Audit And Cleanup

Use the backend audit command to detect stale access records from older data:

cd backend
npm run check:public-access

It reports:

  • RBAC permissions assigned to roles named Public
  • custom permissions assigned to Public users
  • private production presentation grants assigned to non-Public users

To clean those stale records after reviewing the output:

npm run fix:public-access

The fix removes only the stale Public-role permissions, Public-user custom permissions, and non-Public private presentation grants.

Frontend Behavior

If runtime data loading receives 401, /p/[slug] redirects to:

/login?next=/p/[slug]

After login, login.tsx redirects to next when it is a safe local path. If a Public customer user reaches authenticated application pages, LayoutAuthenticated redirects to the first private production slug returned by /api/auth/me; if the Public user has no private presentation grants, it redirects to /.

Public production presentations remain accessible to anonymous users, platform staff, and logged-in customer users.