diff --git a/components/files/ListFiles.tsx b/components/files/ListFiles.tsx index be9bcf0..b4f9162 100644 --- a/components/files/ListFiles.tsx +++ b/components/files/ListFiles.tsx @@ -4,24 +4,24 @@ import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts'; interface ListFilesProps { directories: Directory[]; files: DirectoryFile[]; - onClickDeleteDirectory: (parentPath: string, name: string) => Promise; - onClickDeleteFile: (parentPath: string, name: string) => Promise; onClickOpenRenameDirectory: (parentPath: string, name: string) => void; onClickOpenRenameFile: (parentPath: string, name: string) => void; onClickOpenMoveDirectory: (parentPath: string, name: string) => void; onClickOpenMoveFile: (parentPath: string, name: string) => void; + onClickDeleteDirectory: (parentPath: string, name: string) => Promise; + onClickDeleteFile: (parentPath: string, name: string) => Promise; } export default function ListFiles( { directories, files, - onClickDeleteDirectory, - onClickDeleteFile, onClickOpenRenameDirectory, onClickOpenRenameFile, onClickOpenMoveDirectory, onClickOpenMoveFile, + onClickDeleteDirectory, + onClickDeleteFile, }: ListFilesProps, ) { const dateFormat = new Intl.DateTimeFormat('en-GB', { diff --git a/components/files/MainFiles.tsx b/components/files/MainFiles.tsx index 06598f9..55ae386 100644 --- a/components/files/MainFiles.tsx +++ b/components/files/MainFiles.tsx @@ -452,12 +452,12 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat { const rootPath = join(getFilesRootPath(), userId, path); - // const directoryShares = await db.query(sql`SELECT * FROM "bewcloud_file_shares" - // WHERE "parent_path" = $2 - // AND "type" = 'directory' - // AND ( - // "owner_user_id" = $1 - // OR ANY("user_ids_with_read_access") = $1 - // OR ANY("user_ids_with_write_access") = $1 - // )`, [ - // userId, - // path, - // ]); - - const directoryShares: FileShare[] = []; - - // TODO: Remove this mock test - if (path === '/') { - directoryShares.push({ - id: 'test-ing-123', - owner_user_id: userId, - parent_path: '/', - name: 'Testing', - type: 'directory', - user_ids_with_read_access: [], - user_ids_with_write_access: [], - extra: { - read_share_links: [], - write_share_links: [], - }, - updated_at: new Date('2024-04-01'), - created_at: new Date('2024-03-31'), - }); - } + const directoryShares = await db.query( + sql`SELECT * FROM "bewcloud_file_shares" + WHERE "parent_path" = $2 + AND "type" = 'directory' + AND ( + "owner_user_id" = $1 + OR ANY("user_ids_with_read_access") = $1 + OR ANY("user_ids_with_write_access") = $1 + )`, + [ + userId, + path, + ], + ); const directories: Directory[] = []; @@ -66,7 +48,26 @@ export async function getDirectories(userId: string, path: string): Promise directoryShare.owner_user_id !== userId); + for (const share of foreignDirectoryShares) { + const stat = await Deno.stat(join(getFilesRootPath(), share.owner_user_id, path, share.name)); + + const hasWriteAccess = share.user_ids_with_write_access.includes(userId); + + const directory: Directory = { + owner_user_id: share.owner_user_id, + parent_path: path, + directory_name: share.name, + has_write_access: hasWriteAccess, + file_share: share, + size_in_bytes: stat.size, + updated_at: stat.mtime || new Date(), + created_at: stat.birthtime || new Date(), + }; + + directories.push(directory); + } directories.sort(sortDirectoriesByName); @@ -76,19 +77,20 @@ export async function getDirectories(userId: string, path: string): Promise { const rootPath = join(getFilesRootPath(), userId, path); - // const fileShares = await db.query(sql`SELECT * FROM "bewcloud_file_shares" - // WHERE "parent_path" = $2 - // AND "type" = 'file' - // AND ( - // "owner_user_id" = $1 - // OR ANY("user_ids_with_read_access") = $1 - // OR ANY("user_ids_with_write_access") = $1 - // )`, [ - // userId, - // path, - // ]); - - const fileShares: FileShare[] = []; + const fileShares = await db.query( + sql`SELECT * FROM "bewcloud_file_shares" + WHERE "parent_path" = $2 + AND "type" = 'file' + AND ( + "owner_user_id" = $1 + OR ANY("user_ids_with_read_access") = $1 + OR ANY("user_ids_with_write_access") = $1 + )`, + [ + userId, + path, + ], + ); const files: DirectoryFile[] = []; @@ -113,7 +115,28 @@ export async function getFiles(userId: string, path: string): Promise fileShare.owner_user_id !== userId); + for (const share of foreignFileShares) { + const stat = await Deno.stat(join(getFilesRootPath(), share.owner_user_id, path, share.name)); + + const hasWriteAccess = share.user_ids_with_write_access.includes(userId); + + const file: DirectoryFile = { + owner_user_id: share.owner_user_id, + parent_path: path, + file_name: share.name, + has_write_access: hasWriteAccess, + file_share: share, + size_in_bytes: stat.size, + updated_at: stat.mtime || new Date(), + created_at: stat.birthtime || new Date(), + }; + + files.push(file); + } + + // TODO: Check fileshare directories and list files from there too files.sort(sortFilesByName); @@ -129,7 +152,7 @@ async function getPathEntries(userId: string, path: string): Promise( + sql`UPDATE "bewcloud_file_shares" SET + "parent_path" = $4, + "name" = $5 + WHERE "parent_path" = $2 + AND "name" = $3 + AND "owner_user_id" = $1`, + [ + userId, + oldPath, + oldName, + newPath, + newName, + ], + ); } catch (error) { console.error(error); return false; @@ -190,7 +228,18 @@ export async function deleteDirectoryOrFile(userId: string, path: string, name: const trashPath = join(getFilesRootPath(), userId, TRASH_PATH); await Deno.rename(join(rootPath, name), join(trashPath, name)); - // TODO: Delete any matching file shares + // Delete any matching file shares + await db.query( + sql`DELETE FROM "bewcloud_file_shares" + WHERE "parent_path" = $2 + AND "name" = $3 + AND "owner_user_id" = $1`, + [ + userId, + path, + name, + ], + ); } } catch (error) { console.error(error); @@ -259,3 +308,220 @@ export async function getFile( }; } } + +export async function createFileShare( + userId: string, + path: string, + name: string, + type: 'directory' | 'file', + userIdsWithReadAccess: string[], + userIdsWithWriteAccess: string[], + readShareLinks: FileShareLink[], + writeShareLinks: FileShareLink[], +): Promise { + const extra: FileShare['extra'] = { + read_share_links: readShareLinks, + write_share_links: writeShareLinks, + }; + + const newFileShare = (await db.query( + sql`INSERT INTO "bewcloud_file_shares" ( + "owner_user_id", + "owner_parent_path", + "parent_path", + "name", + "type", + "user_ids_with_read_access", + "user_ids_with_write_access", + "extra" + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [ + userId, + path, + '/', + name, + type, + userIdsWithReadAccess, + userIdsWithWriteAccess, + JSON.stringify(extra), + ], + ))[0]; + + return newFileShare; +} + +export async function updateFileShare(fileShare: FileShare) { + await db.query( + sql`UPDATE "bewcloud_file_shares" SET + "owner_parent_path" = $2, + "parent_path" = $3, + "name" = $4, + "user_ids_with_read_access" = $5, + "user_ids_with_write_access" = $6, + "extra" = $7 + WHERE "id" = $1`, + [ + fileShare.id, + fileShare.owner_parent_path, + fileShare.parent_path, + fileShare.name, + fileShare.user_ids_with_read_access, + fileShare.user_ids_with_write_access, + JSON.stringify(fileShare.extra), + ], + ); +} + +export async function getDirectoryAccess( + userId: string, + parentPath: string, + name?: string, +): Promise<{ hasReadAccess: boolean; hasWriteAccess: boolean; ownerUserId: string; ownerParentPath: string }> { + const rootPath = join(getFilesRootPath(), userId, parentPath, name || ''); + + // If it exists in the correct filesystem path, it's the user's + try { + await Deno.stat(rootPath); + + return { hasReadAccess: true, hasWriteAccess: true, ownerUserId: userId, ownerParentPath: parentPath }; + } catch (error) { + console.error(error); + } + + // Otherwise check if it's been shared with them + const parentPaths: string[] = []; + let nextParentPath: string | null = rootPath; + + while (nextParentPath !== null) { + parentPaths.push(nextParentPath); + + nextParentPath = `/${nextParentPath.split('/').filter(Boolean).slice(0, -1).join('/')}`; + + if (nextParentPath === '/') { + parentPaths.push(nextParentPath); + nextParentPath = null; + } + } + + const fileShare = (await db.query( + sql`SELECT * FROM "bewcloud_file_shares" + WHERE "parent_path" = ANY($2) + AND "name" = $3 + AND "type" = 'directory' + AND ( + ANY("user_ids_with_read_access") = $1 + OR ANY("user_ids_with_write_access") = $1 + ) + ORDER BY "parent_path" ASC + LIMIT 1`, + [ + userId, + parentPaths, + name, + ], + ))[0]; + + if (fileShare) { + return { + hasReadAccess: fileShare.user_ids_with_read_access.includes(userId) || + fileShare.user_ids_with_write_access.includes(userId), + hasWriteAccess: fileShare.user_ids_with_write_access.includes(userId), + ownerUserId: fileShare.owner_user_id, + ownerParentPath: fileShare.owner_parent_path, + }; + } + + return { hasReadAccess: false, hasWriteAccess: false, ownerUserId: userId, ownerParentPath: parentPath }; +} + +export async function getFileAccess( + userId: string, + parentPath: string, + name: string, +): Promise<{ hasReadAccess: boolean; hasWriteAccess: boolean; ownerUserId: string; ownerParentPath: string }> { + const rootPath = join(getFilesRootPath(), userId, parentPath, name); + + // If it exists in the correct filesystem path, it's the user's + try { + await Deno.stat(rootPath); + + return { hasReadAccess: true, hasWriteAccess: true, ownerUserId: userId, ownerParentPath: parentPath }; + } catch (error) { + console.error(error); + } + + // Otherwise check if it's been shared with them + let fileShare = (await db.query( + sql`SELECT * FROM "bewcloud_file_shares" + WHERE "parent_path" = $2 + AND "name" = $3 + AND "type" = 'file' + AND ( + ANY("user_ids_with_read_access") = $1 + OR ANY("user_ids_with_write_access") = $1 + ) + ORDER BY "parent_path" ASC + LIMIT 1`, + [ + userId, + parentPath, + name, + ], + ))[0]; + + if (fileShare) { + return { + hasReadAccess: fileShare.user_ids_with_read_access.includes(userId) || + fileShare.user_ids_with_write_access.includes(userId), + hasWriteAccess: fileShare.user_ids_with_write_access.includes(userId), + ownerUserId: fileShare.owner_user_id, + ownerParentPath: fileShare.owner_parent_path, + }; + } + + // Otherwise check if it's a parent directory has been shared with them, which would also give them access + const parentPaths: string[] = []; + let nextParentPath: string | null = rootPath; + + while (nextParentPath !== null) { + parentPaths.push(nextParentPath); + + nextParentPath = `/${nextParentPath.split('/').filter(Boolean).slice(0, -1).join('/')}`; + + if (nextParentPath === '/') { + parentPaths.push(nextParentPath); + nextParentPath = null; + } + } + + fileShare = (await db.query( + sql`SELECT * FROM "bewcloud_file_shares" + WHERE "parent_path" = ANY($2) + AND "name" = $3 + AND "type" = 'directory' + AND ( + ANY("user_ids_with_read_access") = $1 + OR ANY("user_ids_with_write_access") = $1 + ) + ORDER BY "parent_path" ASC + LIMIT 1`, + [ + userId, + parentPaths, + name, + ], + ))[0]; + + if (fileShare) { + return { + hasReadAccess: fileShare.user_ids_with_read_access.includes(userId) || + fileShare.user_ids_with_write_access.includes(userId), + hasWriteAccess: fileShare.user_ids_with_write_access.includes(userId), + ownerUserId: fileShare.owner_user_id, + ownerParentPath: fileShare.owner_parent_path, + }; + } + + return { hasReadAccess: false, hasWriteAccess: false, ownerUserId: userId, ownerParentPath: parentPath }; +} diff --git a/lib/types.ts b/lib/types.ts index 96a2d81..cdfa8b9 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -85,7 +85,7 @@ export interface NewsFeedArticle { created_at: Date; } -export interface DirectoryOrFileShareLink { +export interface FileShareLink { url: string; hashed_password: string; } @@ -93,16 +93,16 @@ export interface DirectoryOrFileShareLink { export interface FileShare { id: string; owner_user_id: string; + owner_parent_path: string; parent_path: string; name: string; type: 'directory' | 'file'; user_ids_with_read_access: string[]; user_ids_with_write_access: string[]; extra: { - read_share_links: DirectoryOrFileShareLink[]; - write_share_links: DirectoryOrFileShareLink[]; + read_share_links: FileShareLink[]; + write_share_links: FileShareLink[]; }; - updated_at: Date; created_at: Date; } diff --git a/routes/api/files/create-directory.tsx b/routes/api/files/create-directory.tsx index 910be36..eb49787 100644 --- a/routes/api/files/create-directory.tsx +++ b/routes/api/files/create-directory.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { Directory, FreshContextState } from '/lib/types.ts'; -import { createDirectory, getDirectories } from '/lib/data/files.ts'; +import { createDirectory, getDirectories, getDirectoryAccess } from '/lib/data/files.ts'; interface Data {} @@ -30,14 +30,22 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to path and get the appropriate ownerUserId - - const createdDirectory = await createDirectory( + const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess( context.state.user.id, requestBody.parentPath, requestBody.name.trim(), ); + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + + const createdDirectory = await createDirectory( + ownerUserId, + ownerParentPath, + requestBody.name.trim(), + ); + const newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath); const responseBody: ResponseBody = { success: createdDirectory, newDirectories }; diff --git a/routes/api/files/delete-directory.tsx b/routes/api/files/delete-directory.tsx index 8499298..e9c868b 100644 --- a/routes/api/files/delete-directory.tsx +++ b/routes/api/files/delete-directory.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { Directory, FreshContextState } from '/lib/types.ts'; -import { deleteDirectoryOrFile, getDirectories } from '/lib/data/files.ts'; +import { deleteDirectoryOrFile, getDirectories, getDirectoryAccess } from '/lib/data/files.ts'; interface Data {} @@ -30,14 +30,22 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to path and get the appropriate ownerUserId - - const deletedDirectory = await deleteDirectoryOrFile( + const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess( context.state.user.id, requestBody.parentPath, requestBody.name.trim(), ); + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + + const deletedDirectory = await deleteDirectoryOrFile( + ownerUserId, + ownerParentPath, + requestBody.name.trim(), + ); + const newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath); const responseBody: ResponseBody = { success: deletedDirectory, newDirectories }; diff --git a/routes/api/files/delete.tsx b/routes/api/files/delete.tsx index 26ecd03..e93be1c 100644 --- a/routes/api/files/delete.tsx +++ b/routes/api/files/delete.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { DirectoryFile, FreshContextState } from '/lib/types.ts'; -import { deleteDirectoryOrFile, getFiles } from '/lib/data/files.ts'; +import { deleteDirectoryOrFile, getDirectoryAccess, getFileAccess, getFiles } from '/lib/data/files.ts'; interface Data {} @@ -30,14 +30,30 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to path/file and get the appropriate ownerUserId - - const deletedFile = await deleteDirectoryOrFile( + let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess( context.state.user.id, requestBody.parentPath, requestBody.name.trim(), ); + if (!hasWriteAccess) { + const directoryAccessResult = await getDirectoryAccess(context.state.user.id, requestBody.parentPath); + + hasWriteAccess = directoryAccessResult.hasWriteAccess; + ownerUserId = directoryAccessResult.ownerUserId; + ownerParentPath = directoryAccessResult.ownerParentPath; + + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + } + + const deletedFile = await deleteDirectoryOrFile( + ownerUserId, + ownerParentPath, + requestBody.name.trim(), + ); + const newFiles = await getFiles(context.state.user.id, requestBody.parentPath); const responseBody: ResponseBody = { success: deletedFile, newFiles }; diff --git a/routes/api/files/move-directory.tsx b/routes/api/files/move-directory.tsx index 54ef239..08029b9 100644 --- a/routes/api/files/move-directory.tsx +++ b/routes/api/files/move-directory.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { Directory, FreshContextState } from '/lib/types.ts'; -import { getDirectories, renameDirectoryOrFile } from '/lib/data/files.ts'; +import { getDirectories, getDirectoryAccess, renameDirectoryOrFile } from '/lib/data/files.ts'; interface Data {} @@ -33,12 +33,26 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to old and new paths and get the appropriate ownerUserIds + const { hasWriteAccess: hasOldWriteAccess, ownerUserId, ownerParentPath: oldOwnerParentPath } = + await getDirectoryAccess(context.state.user.id, requestBody.oldParentPath, requestBody.name.trim()); + + if (!hasOldWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + + const { hasWriteAccess: hasNewWriteAccess, ownerParentPath: newOwnerParentPath } = await getDirectoryAccess( + context.state.user.id, + requestBody.newParentPath, + ); + + if (!hasNewWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } const movedDirectory = await renameDirectoryOrFile( - context.state.user.id, - requestBody.oldParentPath, - requestBody.newParentPath, + ownerUserId, + oldOwnerParentPath, + newOwnerParentPath, requestBody.name.trim(), requestBody.name.trim(), ); diff --git a/routes/api/files/move.tsx b/routes/api/files/move.tsx index 96a1562..9a047b1 100644 --- a/routes/api/files/move.tsx +++ b/routes/api/files/move.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { DirectoryFile, FreshContextState } from '/lib/types.ts'; -import { getFiles, renameDirectoryOrFile } from '/lib/data/files.ts'; +import { getDirectoryAccess, getFileAccess, getFiles, renameDirectoryOrFile } from '/lib/data/files.ts'; interface Data {} @@ -33,12 +33,35 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to old and new paths/files and get the appropriate ownerUserIds - - const movedFile = await renameDirectoryOrFile( + let { hasWriteAccess: hasOldWriteAccess, ownerUserId, ownerParentPath: oldOwnerParentPath } = await getFileAccess( context.state.user.id, requestBody.oldParentPath, + requestBody.name.trim(), + ); + + if (!hasOldWriteAccess) { + const directoryAccessResult = await getDirectoryAccess(context.state.user.id, requestBody.oldParentPath); + + hasOldWriteAccess = directoryAccessResult.hasWriteAccess; + ownerUserId = directoryAccessResult.ownerUserId; + oldOwnerParentPath = directoryAccessResult.ownerParentPath; + + return new Response('Forbidden', { status: 403 }); + } + + const { hasWriteAccess: hasNewWriteAccess, ownerParentPath: newOwnerParentPath } = await getDirectoryAccess( + context.state.user.id, requestBody.newParentPath, + ); + + if (!hasNewWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + + const movedFile = await renameDirectoryOrFile( + ownerUserId, + oldOwnerParentPath, + newOwnerParentPath, requestBody.name.trim(), requestBody.name.trim(), ); diff --git a/routes/api/files/rename-directory.tsx b/routes/api/files/rename-directory.tsx index fd1a7cd..7a5dadf 100644 --- a/routes/api/files/rename-directory.tsx +++ b/routes/api/files/rename-directory.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { Directory, FreshContextState } from '/lib/types.ts'; -import { getDirectories, renameDirectoryOrFile } from '/lib/data/files.ts'; +import { getDirectories, getDirectoryAccess, renameDirectoryOrFile } from '/lib/data/files.ts'; interface Data {} @@ -32,12 +32,20 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to path and get the appropriate ownerUserId - - const movedDirectory = await renameDirectoryOrFile( + const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess( context.state.user.id, requestBody.parentPath, - requestBody.parentPath, + requestBody.oldName.trim(), + ); + + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + + const movedDirectory = await renameDirectoryOrFile( + ownerUserId, + ownerParentPath, + ownerParentPath, requestBody.oldName.trim(), requestBody.newName.trim(), ); diff --git a/routes/api/files/rename.tsx b/routes/api/files/rename.tsx index cca14d5..fdf125d 100644 --- a/routes/api/files/rename.tsx +++ b/routes/api/files/rename.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { DirectoryFile, FreshContextState } from '/lib/types.ts'; -import { getFiles, renameDirectoryOrFile } from '/lib/data/files.ts'; +import { getDirectoryAccess, getFileAccess, getFiles, renameDirectoryOrFile } from '/lib/data/files.ts'; interface Data {} @@ -32,12 +32,28 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to path/file and get the appropriate ownerUserId - - const movedFile = await renameDirectoryOrFile( + let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess( context.state.user.id, requestBody.parentPath, - requestBody.parentPath, + requestBody.oldName.trim(), + ); + + if (!hasWriteAccess) { + const directoryAccessResult = await getDirectoryAccess(context.state.user.id, requestBody.parentPath); + + hasWriteAccess = directoryAccessResult.hasWriteAccess; + ownerUserId = directoryAccessResult.ownerUserId; + ownerParentPath = directoryAccessResult.ownerParentPath; + + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + } + + const movedFile = await renameDirectoryOrFile( + ownerUserId, + ownerParentPath, + ownerParentPath, requestBody.oldName.trim(), requestBody.newName.trim(), ); diff --git a/routes/api/files/upload.tsx b/routes/api/files/upload.tsx index d20980c..d78b13c 100644 --- a/routes/api/files/upload.tsx +++ b/routes/api/files/upload.tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { DirectoryFile, FreshContextState } from '/lib/types.ts'; -import { createFile, getFiles } from '/lib/data/files.ts'; +import { createFile, getDirectoryAccess, getFileAccess, getFiles } from '/lib/data/files.ts'; interface Data {} @@ -29,9 +29,25 @@ export const handler: Handlers = { return new Response('Bad Request', { status: 400 }); } - // TODO: Verify user has write access to path and get the appropriate ownerUserId + let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess( + context.state.user.id, + parentPath, + name.trim(), + ); - const createdFile = await createFile(context.state.user.id, parentPath, name.trim(), await contents.arrayBuffer()); + if (!hasWriteAccess) { + const directoryAccessResult = await getDirectoryAccess(context.state.user.id, parentPath); + + hasWriteAccess = directoryAccessResult.hasWriteAccess; + ownerUserId = directoryAccessResult.ownerUserId; + ownerParentPath = directoryAccessResult.ownerParentPath; + + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + } + + const createdFile = await createFile(ownerUserId, ownerParentPath, name.trim(), await contents.arrayBuffer()); const newFiles = await getFiles(context.state.user.id, parentPath); diff --git a/routes/files/open/[fileName].tsx b/routes/files/open/[fileName].tsx index 09da8f1..dda10ed 100644 --- a/routes/files/open/[fileName].tsx +++ b/routes/files/open/[fileName].tsx @@ -1,7 +1,7 @@ import { Handlers } from 'fresh/server.ts'; import { FreshContextState } from '/lib/types.ts'; -import { getFile } from '/lib/data/files.ts'; +import { getFile, getDirectoryAccess, getFileAccess } from '/lib/data/files.ts'; interface Data {} @@ -31,9 +31,25 @@ export const handler: Handlers = { currentPath = `${currentPath}/`; } - // TODO: Verify user has read or write access to path/file and get the appropriate ownerUserId + let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess( + context.state.user.id, + currentPath, + decodeURIComponent(fileName), + ); - const fileResult = await getFile(context.state.user.id, currentPath, decodeURIComponent(fileName)); + if (!hasWriteAccess) { + const directoryAccessResult = await getDirectoryAccess(context.state.user.id, currentPath); + + hasWriteAccess = directoryAccessResult.hasWriteAccess; + ownerUserId = directoryAccessResult.ownerUserId; + ownerParentPath = directoryAccessResult.ownerParentPath; + + if (!hasWriteAccess) { + return new Response('Forbidden', { status: 403 }); + } + } + + const fileResult = await getFile(ownerUserId, ownerParentPath, decodeURIComponent(fileName)); if (!fileResult.success) { return new Response('Not Found', { status: 404 });