# 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: ```bash 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: ```bash 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: ```text /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.