-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(web): verify space membership before listing space and folder videos #2031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
97942b3
5c58b58
26d47dc
02ecc47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,8 @@ import { db } from "@cap/database"; | |
| import { getCurrentUser } from "@cap/database/auth/session"; | ||
| import { sharedVideos, spaceVideos } from "@cap/database/schema"; | ||
| import type { Folder, Space, Video } from "@cap/web-domain"; | ||
| import { eq } from "drizzle-orm"; | ||
| import { and, eq } from "drizzle-orm"; | ||
| import { getSpaceAccess } from "@/actions/organization/space-authorization"; | ||
|
|
||
| export async function getFolderVideoIds( | ||
| folderId: Folder.FolderId, | ||
|
|
@@ -21,17 +22,47 @@ export async function getFolderVideoIds( | |
| throw new Error("Folder ID is required"); | ||
| } | ||
|
|
||
| if (!spaceId) { | ||
| throw new Error("Space ID is required"); | ||
| } | ||
|
|
||
| const isAllSpacesEntry = user.activeOrganizationId === spaceId; | ||
|
|
||
| // `spaceId` is caller-supplied, so the space branch needs a membership | ||
| // check. The all-spaces branch compares against `activeOrganizationId`, | ||
| // which is read from the user record, so it is already scoped. | ||
| // | ||
| // The membership check alone is not sufficient: `folderId` is also | ||
| // caller-supplied, so the queries below must be constrained to the space | ||
| // (or org) we just authorized. Otherwise a caller could pass a space they | ||
| // legitimately belong to together with a folder from another space and | ||
| // still read its contents. | ||
| if (!isAllSpacesEntry) { | ||
| const access = await getSpaceAccess(user.id, spaceId); | ||
| if (!access || (!access.organizationRole && !access.spaceRole)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good hardening. One concern: this new access check keys off the caller-supplied |
||
| throw new Error("Folder not found"); | ||
| } | ||
| } | ||
|
|
||
| const rows = isAllSpacesEntry | ||
| ? await db() | ||
| .select({ id: sharedVideos.videoId }) | ||
| .from(sharedVideos) | ||
| .where(eq(sharedVideos.folderId, folderId)) | ||
| .where( | ||
| and( | ||
| eq(sharedVideos.folderId, folderId), | ||
| eq(sharedVideos.organizationId, user.activeOrganizationId), | ||
| ), | ||
| ) | ||
| : await db() | ||
| .select({ id: spaceVideos.videoId }) | ||
| .from(spaceVideos) | ||
| .where(eq(spaceVideos.folderId, folderId)); | ||
| .where( | ||
| and( | ||
| eq(spaceVideos.folderId, folderId), | ||
| eq(spaceVideos.spaceId, spaceId), | ||
| ), | ||
| ); | ||
|
|
||
| return { | ||
| success: true, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: consider validating
spaceIdup front (likegetSpaceVideoIds), so a missing/empty value doesn't fall through togetSpaceAccessand return a misleading "Folder not found".