Per-space viewer controls#1795
Conversation
|
Paragon Review Skipped Hi @richiemcilroy! Your Polarity credit balance is insufficient to complete this review. Please visit https://app.paragon.run to finish your review. |
| const processedVideoData = yield* Effect.all( | ||
| videoData.map( | ||
| Effect.fn(function* (video) { | ||
| const sharedSpaces = sharedSpacesMap[video.id] ?? []; | ||
| const rules = resolveEffectiveVideoRules({ | ||
| videoSettings: video.settings, | ||
| organizationSettings: null, | ||
| spaces: sharedSpaces.filter((space) => !space.isOrg), | ||
| }); |
There was a problem hiding this comment.
Org settings not passed to
resolveEffectiveVideoRules
organizationSettings is hardcoded to null here (and identically in spaces/[spaceId]/page.tsx). If an org has disabled, say, disableCaptions at the org level, the dashboard card will compute settings.disableCaptions = false — showing a different state than what viewers actually see on the share/embed pages, which correctly receive org settings. An owner could be misled about effective settings for their video.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/lib/folder.ts
Line: 262-270
Comment:
**Org settings not passed to `resolveEffectiveVideoRules`**
`organizationSettings` is hardcoded to `null` here (and identically in `spaces/[spaceId]/page.tsx`). If an org has disabled, say, `disableCaptions` at the org level, the dashboard card will compute `settings.disableCaptions = false` — showing a different state than what viewers actually see on the share/embed pages, which correctly receive org settings. An owner could be misled about effective settings for their video.
How can I resolve this? If you propose a fix, please make it concise.| const settingKeys = [ | ||
| "disableSummary", | ||
| "disableCaptions", | ||
| "disableChapters", | ||
| "disableReactions", | ||
| "disableTranscript", | ||
| "disableComments", | ||
| ] as const; | ||
|
|
||
| const getSettingsFromFormData = (formData: FormData) => | ||
| Object.fromEntries( | ||
| settingKeys.map((key) => [key, formData.get(key) === "true"]), | ||
| ); | ||
|
|
||
| const proSettingKeys = [ | ||
| "disableSummary", | ||
| "disableChapters", | ||
| "disableTranscript", | ||
| ] as const; |
There was a problem hiding this comment.
Duplicate setting-key arrays silently diverge from
create-space.ts
settingKeys and proSettingKeys are defined verbatim in both create-space.ts and update-space.ts. If a new viewer-control key (e.g. disableDownload) is added to one file but missed in the other, create will honour it while update silently drops it (or vice-versa). Consider extracting these into a shared constants module so both actions stay in sync automatically.
| const settingKeys = [ | |
| "disableSummary", | |
| "disableCaptions", | |
| "disableChapters", | |
| "disableReactions", | |
| "disableTranscript", | |
| "disableComments", | |
| ] as const; | |
| const getSettingsFromFormData = (formData: FormData) => | |
| Object.fromEntries( | |
| settingKeys.map((key) => [key, formData.get(key) === "true"]), | |
| ); | |
| const proSettingKeys = [ | |
| "disableSummary", | |
| "disableChapters", | |
| "disableTranscript", | |
| ] as const; | |
| // TODO: extract these into a shared constants module (e.g. lib/space-settings.ts) | |
| // that both create-space.ts and update-space.ts import, so adding a new key | |
| // only needs to happen in one place. | |
| const settingKeys = [ | |
| "disableSummary", | |
| "disableCaptions", | |
| "disableChapters", | |
| "disableReactions", | |
| "disableTranscript", | |
| "disableComments", | |
| ] as const; | |
| const getSettingsFromFormData = (formData: FormData) => | |
| Object.fromEntries( | |
| settingKeys.map((key) => [key, formData.get(key) === "true"]), | |
| ); | |
| const proSettingKeys = [ | |
| "disableSummary", | |
| "disableChapters", | |
| "disableTranscript", | |
| ] as const; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/update-space.ts
Line: 22-40
Comment:
**Duplicate setting-key arrays silently diverge from `create-space.ts`**
`settingKeys` and `proSettingKeys` are defined verbatim in both `create-space.ts` and `update-space.ts`. If a new viewer-control key (e.g. `disableDownload`) is added to one file but missed in the other, create will honour it while update silently drops it (or vice-versa). Consider extracting these into a shared constants module so both actions stay in sync automatically.
```suggestion
// TODO: extract these into a shared constants module (e.g. lib/space-settings.ts)
// that both create-space.ts and update-space.ts import, so adding a new key
// only needs to happen in one place.
const settingKeys = [
"disableSummary",
"disableCaptions",
"disableChapters",
"disableReactions",
"disableTranscript",
"disableComments",
] as const;
const getSettingsFromFormData = (formData: FormData) =>
Object.fromEntries(
settingKeys.map((key) => [key, formData.get(key) === "true"]),
);
const proSettingKeys = [
"disableSummary",
"disableChapters",
"disableTranscript",
] as const;
```
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| if (!isCreator && !membership) { | ||
| if (!isCreator && membership?.role !== "Admin") { | ||
| return { success: false, error: "Unauthorized" }; | ||
| } |
There was a problem hiding this comment.
Authorization tightened from "any member" to "Admin-role only"
Previously any space member could submit the update form. This PR changes the guard to membership?.role !== "Admin", so regular (non-admin) members can no longer rename a space, change viewer settings, or manage the member list. This is a sensible security improvement, but it's a silent behaviour change that could break existing UX flows where regular members were previously able to update the space. Worth documenting in the PR or confirming it's intentional.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/update-space.ts
Line: 87-90
Comment:
**Authorization tightened from "any member" to "Admin-role only"**
Previously any space member could submit the update form. This PR changes the guard to `membership?.role !== "Admin"`, so regular (non-admin) members can no longer rename a space, change viewer settings, or manage the member list. This is a sensible security improvement, but it's a silent behaviour change that could break existing UX flows where regular members were previously able to update the space. Worth documenting in the PR or confirming it's intentional.
How can I resolve this? If you propose a fix, please make it concise.|
hey! please re-review the pr @greptileai |
|
please re-review the pr @greptileai |
This PR adds per-space viewer controls (summary, captions, chapters, reactions, transcript, comments) and optional space-level password protection, then merges those rules with video and org settings so the dashboard, share page, and embed player reflect the effective policy.
Greptile Summary
This PR introduces per-space viewer controls (summary, captions, chapters, reactions, transcript, comments) and optional space-level password protection, merging those rules with video and org settings so the dashboard, share page, and embed player all reflect the effective policy.
EffectiveVideoRules.tsis a new pure function that merges video, org, and space-level viewer settings; it is well-tested and cleanly separated from I/O.VideosPolicy.tsnow fetches space password hashes before the membership check so any space password gates access (note: this also affects explicit space members, discussed in a prior review comment).settings(JSON) andpassword(encrypted text) columns to thespacestable, withcreate-space.ts/update-space.tshandling pro-tier gating and the sharedspace-settings.tshelper keeping both actions in sync.Confidence Score: 4/5
Safe to merge with minor attention to org-settings scoping in the caps dashboard page.
The core policy and rules logic is solid and well-covered by unit tests. The one inconsistency is that caps/page.tsx resolves org settings from user.activeOrganizationId for all videos, while folder.ts correctly fetches per unique video.orgId. For users whose active org matches every video's org this makes no difference; for multi-org accounts it would silently compute the wrong effective viewer policy on the caps dashboard.
apps/web/app/(org)/dashboard/caps/page.tsx — single-org settings lookup should be replaced with per-video org lookup to match the pattern in folder.ts.
Important Files Changed
Comments Outside Diff (1)
packages/web-backend/src/Videos/VideosPolicy.ts, line 82-91 (link)buildCanViewnow callsverifyPasswordCandidates(video, passwordHashes)for org/space members wherepasswordHashesincludes every space-level password hash. Because the only way to satisfy this check is via thex-cap-passwordcookie (set only through theverifyVideoPasswordaction), a user who is an explicit member of a space but has never manually entered that space's password will receive aVerifyVideoPasswordErrorand be denied access to any video inside that password-protected space.The test
"requires a space password for space members"confirms this is the intentional design, but it is a sharp behavioral change: previously, having org/space membership granted access to unpassworded videos unconditionally. Now even members must go through the password-entry flow if their space has a password set, but they may never be shown that UI prompt. If the design intent is that space passwords are exclusively for external viewers, consider only passing the video-level password hash toverifyPasswordCandidatesfor the member branch, or skipping the call entirely for confirmed members.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "fix: restore typecheck setup" | Re-trigger Greptile