fix(web): require an explicit share to download, matching the view policy#2038
fix(web): require an explicit share to download, matching the view policy#2038DPS0340 wants to merge 2 commits into
Conversation
|
@tembo please review |
|
Note on the red check so it isn't read as a build break:
That is the preview-deploy authorization gate for an outside contributor, not a compile or test failure. My other open PRs on this repo show the identical status. The verification I ran locally on this exact diff is in the PR body, including the revert check confirming the 2 new behavioural tests fail against the current implementation and the other 4 pass under both. |
| ownerId, | ||
| videoId, | ||
| orgId, | ||
| }: { |
There was a problem hiding this comment.
Looks good overall. One thing: this signature change still needs a call-site update in apps/web/app/s/[videoId]/page.tsx (it still passes orgId: video.orgId), otherwise this will fail typecheck/build.
|
@tembo please review |
|
Good catch, and it would have broken the build — fixed in I checked the call sites with Both call sites now pass only One thing I confirmed before removing the line: The behavioural point matters more here than in the server action, actually — @tembo please re-review. |
|
Closing this one first, and it is the clearest cut of the four. #2038 closes #2037 — but I filed #2037 myself. Every other PR I am keeping closes an issue a real user reported (@geo-chen, @rlnt, @pulgueta, @shreyastaware). This one is me reporting a thing and then fixing my own report, which is exactly the kind of item that should not be taking up space in your review queue ahead of theirs. The underlying observation still stands as far as I can tell: I have left #2037 open as the question. If you say "yes, download should match view", I will reopen this against current Trimming my open PRs from nine to five; the other three closures are for the same reason (queue size, no defect found). |
What
canUserDownloadVideono longer treats the video's own organization as a share target. Download permission now requires the same thing view permission requires: ownership, an explicitsharedVideosrow for an org the user belongs to, or an explicitspaceVideosrow for a space they belong to.Closes #2037.
The problem
orgIdis passed fromvideo.orgIdat the call site inactions/videos/download.ts. That is the org the video belongs to, not one it was shared with — so membership of the owner's org was enough to download anything that org owns, and thesharedVideoslookup above it was redundant for that org.The view policy next door is stricter.
buildCanViewinpackages/web-backend/src/Videos/VideosPolicy.tsgoes throughorgsRepo.membershipForVideo, which joins throughsharedVideosand requires the share row to exist:There,
video.orgIdis used only to look up the allowed email domain, never as a grant.So the two paths disagreed: a colleague in the owner's org who cannot view an unshared private video could still download it. The space branch of the same function was already written the stricter way — it derives space ids purely from
spaceVideosrows and returnsfalsewhen there are none — which is part of why the org branch reads as an oversight.The change
The org membership lookup now runs only when
sharedVideosreturned rows, and only against those org ids. As a side effect it skips a query entirely when the video has no org shares, which is the common case for a personal recording.orgIdbecomes unused, so it is dropped from the signature and from the single call site.Verification
I want to be precise about what I did and did not run.
What I ran. I could not install the full monorepo toolchain, so I ran the committed test file, unmodified, against the real
video-download-permissions.tsin an isolated vitest project with the same@/libalias this repo'svitest.config.tsdefines. Six tests, all passing:Revert check. I restored the original implementation (with
orgIdand the unconditionalorgIds) and re-ran the same tests: exactly 2 fail. The other 4 pass under both implementations, so they confirm the change did not simply make the function refuse more things.Direct proof of the old behaviour. With the original code, an unshared private video plus a colleague who is a member of the owner's org returns:
What I did not do. I have not run this against a live Cap instance with a seeded database, so the query-shape reasoning is from reading the drizzle chains rather than from observed SQL.
video-download-permissions.tshad no test file before this, so nothing pinned either interpretation.If
video.orgIdwas meant to be a grant and the view policy is deliberately the stricter of the two, then this is a docs gap rather than a bug, and I would much rather be told that than have it merged. The test file is written so it documents whichever rule you land on.