-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix(web): add ownership checks to folder, space and org video actions #1928
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
base: main
Are you sure you want to change the base?
Changes from all commits
d2348b1
97c3abc
a27d0cc
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 | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,9 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Folder, Space, Video } from "@cap/web-domain"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { and, eq } from "drizzle-orm"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { revalidatePath } from "next/cache"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { requireOrganizationSettingsManager } from "@/actions/organization/authorization"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. If the goal is to allow regular org members to move their own shared videos from the “All Spaces” view, this import can switch to
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getSpaceAccess } from "@/actions/organization/space-authorization"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function moveVideoToFolder({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
richiemcilroy marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| videoId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| folderId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,10 +39,18 @@ export async function moveVideoToFolder({ | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const isAllSpacesEntry = spaceId === user.activeOrganizationId; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // If folderId is provided, verify it exists and belongs to the same organization | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // If a destination folder is provided, load it once (scoped to the caller's | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // org) so each branch can also verify the caller may WRITE to that specific | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // folder — not just that the source space/folder is manageable. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let destinationFolder: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | { spaceId: string | null; createdById: string } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (folderId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const [folder] = await db() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .select() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| [destinationFolder] = await db() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .select({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| spaceId: folders.spaceId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| createdById: folders.createdById, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(folders) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| and( | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -48,12 +59,22 @@ export async function moveVideoToFolder({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!folder) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!destinationFolder) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Folder not found or not accessible"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (spaceId && !isAllSpacesEntry) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const access = await getSpaceAccess(user.id, spaceId); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!access?.canManage) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("You don't have permission to manage this space"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // The destination folder must belong to the same space being managed. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (destinationFolder && destinationFolder.spaceId !== spaceId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Folder not found or not accessible"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| await db() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
richiemcilroy marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| .update(spaceVideos) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .set({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -63,6 +84,16 @@ export async function moveVideoToFolder({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| and(eq(spaceVideos.videoId, videoId), eq(spaceVideos.spaceId, spaceId)), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (spaceId && isAllSpacesEntry) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| await requireOrganizationSettingsManager( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| user.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| user.activeOrganizationId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // The destination must be an org-level (non-space) folder. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (destinationFolder && destinationFolder.spaceId !== null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Folder not found or not accessible"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| await db() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .update(sharedVideos) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .set({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -75,12 +106,21 @@ export async function moveVideoToFolder({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
86
to
107
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.
Consider using org membership + scoping the UPDATE to
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Personal move: the destination must be the caller's own personal folder. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| destinationFolder && | ||||||||||||||||||||||||||||||||||||||||||||||||||
| (destinationFolder.spaceId !== null || | ||||||||||||||||||||||||||||||||||||||||||||||||||
| destinationFolder.createdById !== user.id) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Folder not found or not accessible"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| await db() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .update(videos) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .set({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| folderId: folderId === null ? null : folderId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .where(eq(videos.id, videoId)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .where(and(eq(videos.id, videoId), eq(videos.ownerId, user.id))); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Always revalidate the main caps page | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
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.
user.activeOrganizationId. InmoveVideoToFolder.tsthe equivalent query correctly addseq(folders.organizationId, user.activeOrganizationId)to the WHERE clause. Without it, a caller who created a personal folder in a different organisation (or holdscanManageover a space in another org) can pass that foreignfolderId, pass the ownership check, and the subsequent inserts will writesharedVideos/spaceVideosrows for the current org that carry afolderIdpointing to another org's folder — corrupting cross-org FK references.Prompt To Fix With AI