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:
projectIduserId- 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.tshandles slug lookup and private-presentation listing.backend/src/services/access-policy.tsowns the final access decision throughcanViewProductionPresentation(user, projectSlug).
The service determines:
- whether the project slug points to a private production presentation
- whether an authenticated user can use admin APIs (
Publicusers are never treated as staff, even if stale permissions exist) - whether a customer user has a
production_presentation_accessrow - 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
GETrequests return401 - authenticated users with any RBAC permission can access all private production presentations as platform staff
- authenticated users with
Publicrole and no permissions must have an access row for the project - authenticated but unauthorized users receive
403 - authorized users are marked as
req.isRuntimePublicRequest = trueso 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 createproduction_presentation_accessrows - if the selected role is not
Public, selected project IDs are ignored
On edit submit:
- existing
production_presentation_accessrows for that user are replaced by the selected private project IDs - switching a user away from
Publicclears 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.