Advanced file sharing

This is a WIP for advanced file sharing, but I won't pursue this for now since using symlinks in the file system works for me, and this is adding a ton of complexity I don't want or need right now.
This commit is contained in:
Bruno Bernardino
2024-04-04 12:52:44 +01:00
parent 4e5fdd569a
commit a8a0e20428
14 changed files with 547 additions and 100 deletions

View File

@@ -4,24 +4,24 @@ import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';
interface ListFilesProps { interface ListFilesProps {
directories: Directory[]; directories: Directory[];
files: DirectoryFile[]; files: DirectoryFile[];
onClickDeleteDirectory: (parentPath: string, name: string) => Promise<void>;
onClickDeleteFile: (parentPath: string, name: string) => Promise<void>;
onClickOpenRenameDirectory: (parentPath: string, name: string) => void; onClickOpenRenameDirectory: (parentPath: string, name: string) => void;
onClickOpenRenameFile: (parentPath: string, name: string) => void; onClickOpenRenameFile: (parentPath: string, name: string) => void;
onClickOpenMoveDirectory: (parentPath: string, name: string) => void; onClickOpenMoveDirectory: (parentPath: string, name: string) => void;
onClickOpenMoveFile: (parentPath: string, name: string) => void; onClickOpenMoveFile: (parentPath: string, name: string) => void;
onClickDeleteDirectory: (parentPath: string, name: string) => Promise<void>;
onClickDeleteFile: (parentPath: string, name: string) => Promise<void>;
} }
export default function ListFiles( export default function ListFiles(
{ {
directories, directories,
files, files,
onClickDeleteDirectory,
onClickDeleteFile,
onClickOpenRenameDirectory, onClickOpenRenameDirectory,
onClickOpenRenameFile, onClickOpenRenameFile,
onClickOpenMoveDirectory, onClickOpenMoveDirectory,
onClickOpenMoveFile, onClickOpenMoveFile,
onClickDeleteDirectory,
onClickDeleteFile,
}: ListFilesProps, }: ListFilesProps,
) { ) {
const dateFormat = new Intl.DateTimeFormat('en-GB', { const dateFormat = new Intl.DateTimeFormat('en-GB', {

View File

@@ -452,12 +452,12 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
<ListFiles <ListFiles
directories={directories.value} directories={directories.value}
files={files.value} files={files.value}
onClickDeleteDirectory={onClickDeleteDirectory}
onClickDeleteFile={onClickDeleteFile}
onClickOpenRenameDirectory={onClickOpenRenameDirectory} onClickOpenRenameDirectory={onClickOpenRenameDirectory}
onClickOpenRenameFile={onClickOpenRenameFile} onClickOpenRenameFile={onClickOpenRenameFile}
onClickOpenMoveDirectory={onClickOpenMoveDirectory} onClickOpenMoveDirectory={onClickOpenMoveDirectory}
onClickOpenMoveFile={onClickOpenMoveFile} onClickOpenMoveFile={onClickOpenMoveFile}
onClickDeleteDirectory={onClickDeleteDirectory}
onClickDeleteFile={onClickDeleteFile}
/> />
<span <span

View File

@@ -0,0 +1,56 @@
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: bewcloud_file_shares; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.bewcloud_file_shares (
id uuid DEFAULT gen_random_uuid(),
owner_user_id uuid DEFAULT gen_random_uuid(),
owner_parent_path text NOT NULL,
parent_path text NOT NULL,
name text NOT NULL,
type varchar NOT NULL,
user_ids_with_read_access uuid[] NOT NULL,
user_ids_with_write_access uuid[] NOT NULL,
extra jsonb NOT NULL,
created_at timestamp with time zone DEFAULT now()
);
ALTER TABLE public.bewcloud_file_shares OWNER TO postgres;
CREATE UNIQUE INDEX bewcloud_file_shares_unique_index ON public.bewcloud_file_shares ( owner_user_id, owner_parent_path, name, type );
--
-- Name: bewcloud_file_shares bewcloud_file_shares_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.bewcloud_file_shares
ADD CONSTRAINT bewcloud_file_shares_pkey PRIMARY KEY (id);
--
-- Name: bewcloud_file_shares bewcloud_file_shares_owner_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.bewcloud_file_shares
ADD CONSTRAINT bewcloud_file_shares_owner_user_id_fkey FOREIGN KEY (owner_user_id) REFERENCES public.bewcloud_users(id);
--
-- Name: TABLE bewcloud_file_shares; Type: ACL; Schema: public; Owner: postgres
--
GRANT ALL ON TABLE public.bewcloud_file_shares TO postgres;

View File

@@ -1,47 +1,29 @@
import { join } from 'std/path/join.ts'; import { join } from 'std/path/join.ts';
// import Database, { sql } from '/lib/interfaces/database.ts'; import Database, { sql } from '/lib/interfaces/database.ts';
import { getFilesRootPath } from '/lib/config.ts'; import { getFilesRootPath } from '/lib/config.ts';
import { Directory, DirectoryFile, FileShare } from '/lib/types.ts'; import { Directory, DirectoryFile, FileShare, FileShareLink } from '/lib/types.ts';
import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, TRASH_PATH } from '/lib/utils/files.ts'; import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, TRASH_PATH } from '/lib/utils/files.ts';
// const db = new Database(); const db = new Database();
export async function getDirectories(userId: string, path: string): Promise<Directory[]> { export async function getDirectories(userId: string, path: string): Promise<Directory[]> {
const rootPath = join(getFilesRootPath(), userId, path); const rootPath = join(getFilesRootPath(), userId, path);
// const directoryShares = await db.query<FileShare>(sql`SELECT * FROM "bewcloud_file_shares" const directoryShares = await db.query<FileShare>(
// WHERE "parent_path" = $2 sql`SELECT * FROM "bewcloud_file_shares"
// AND "type" = 'directory' WHERE "parent_path" = $2
// AND ( AND "type" = 'directory'
// "owner_user_id" = $1 AND (
// OR ANY("user_ids_with_read_access") = $1 "owner_user_id" = $1
// OR ANY("user_ids_with_write_access") = $1 OR ANY("user_ids_with_read_access") = $1
// )`, [ OR ANY("user_ids_with_write_access") = $1
// userId, )`,
// path, [
// ]); 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 directories: Directory[] = []; const directories: Directory[] = [];
@@ -66,7 +48,26 @@ export async function getDirectories(userId: string, path: string): Promise<Dire
directories.push(directory); directories.push(directory);
} }
// TODO: Add directoryShares that aren't owned by this user // Add directoryShares that aren't owned by this user
const foreignDirectoryShares = directoryShares.filter((directoryShare) => 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); directories.sort(sortDirectoriesByName);
@@ -76,19 +77,20 @@ export async function getDirectories(userId: string, path: string): Promise<Dire
export async function getFiles(userId: string, path: string): Promise<DirectoryFile[]> { export async function getFiles(userId: string, path: string): Promise<DirectoryFile[]> {
const rootPath = join(getFilesRootPath(), userId, path); const rootPath = join(getFilesRootPath(), userId, path);
// const fileShares = await db.query<FileShare>(sql`SELECT * FROM "bewcloud_file_shares" const fileShares = await db.query<FileShare>(
// WHERE "parent_path" = $2 sql`SELECT * FROM "bewcloud_file_shares"
// AND "type" = 'file' WHERE "parent_path" = $2
// AND ( AND "type" = 'file'
// "owner_user_id" = $1 AND (
// OR ANY("user_ids_with_read_access") = $1 "owner_user_id" = $1
// OR ANY("user_ids_with_write_access") = $1 OR ANY("user_ids_with_read_access") = $1
// )`, [ OR ANY("user_ids_with_write_access") = $1
// userId, )`,
// path, [
// ]); userId,
path,
const fileShares: FileShare[] = []; ],
);
const files: DirectoryFile[] = []; const files: DirectoryFile[] = [];
@@ -113,7 +115,28 @@ export async function getFiles(userId: string, path: string): Promise<DirectoryF
files.push(file); files.push(file);
} }
// TODO: Add fileShares that aren't owned by this user // Add fileShares that aren't owned by this user
const foreignFileShares = fileShares.filter((fileShare) => 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); files.sort(sortFilesByName);
@@ -129,7 +152,7 @@ async function getPathEntries(userId: string, path: string): Promise<Deno.DirEnt
await Deno.stat(rootPath); await Deno.stat(rootPath);
} catch (error) { } catch (error) {
if (error.toString().includes('NotFound')) { if (error.toString().includes('NotFound')) {
await Deno.mkdir(rootPath, { recursive: true }); await Deno.mkdir(join(rootPath, TRASH_PATH), { recursive: true });
} }
} }
} }
@@ -171,7 +194,22 @@ export async function renameDirectoryOrFile(
try { try {
await Deno.rename(join(oldRootPath, oldName), join(newRootPath, newName)); await Deno.rename(join(oldRootPath, oldName), join(newRootPath, newName));
// TODO: Update any matching file shares // Update any matching file shares
await db.query<FileShare>(
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) { } catch (error) {
console.error(error); console.error(error);
return false; return false;
@@ -190,7 +228,18 @@ export async function deleteDirectoryOrFile(userId: string, path: string, name:
const trashPath = join(getFilesRootPath(), userId, TRASH_PATH); const trashPath = join(getFilesRootPath(), userId, TRASH_PATH);
await Deno.rename(join(rootPath, name), join(trashPath, name)); await Deno.rename(join(rootPath, name), join(trashPath, name));
// TODO: Delete any matching file shares // Delete any matching file shares
await db.query<FileShare>(
sql`DELETE FROM "bewcloud_file_shares"
WHERE "parent_path" = $2
AND "name" = $3
AND "owner_user_id" = $1`,
[
userId,
path,
name,
],
);
} }
} catch (error) { } catch (error) {
console.error(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<FileShare> {
const extra: FileShare['extra'] = {
read_share_links: readShareLinks,
write_share_links: writeShareLinks,
};
const newFileShare = (await db.query<FileShare>(
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<FileShare>(
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<FileShare>(
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<FileShare>(
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 };
}

View File

@@ -85,7 +85,7 @@ export interface NewsFeedArticle {
created_at: Date; created_at: Date;
} }
export interface DirectoryOrFileShareLink { export interface FileShareLink {
url: string; url: string;
hashed_password: string; hashed_password: string;
} }
@@ -93,16 +93,16 @@ export interface DirectoryOrFileShareLink {
export interface FileShare { export interface FileShare {
id: string; id: string;
owner_user_id: string; owner_user_id: string;
owner_parent_path: string;
parent_path: string; parent_path: string;
name: string; name: string;
type: 'directory' | 'file'; type: 'directory' | 'file';
user_ids_with_read_access: string[]; user_ids_with_read_access: string[];
user_ids_with_write_access: string[]; user_ids_with_write_access: string[];
extra: { extra: {
read_share_links: DirectoryOrFileShareLink[]; read_share_links: FileShareLink[];
write_share_links: DirectoryOrFileShareLink[]; write_share_links: FileShareLink[];
}; };
updated_at: Date;
created_at: Date; created_at: Date;
} }

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { Directory, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -30,14 +30,22 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
// TODO: Verify user has write access to path and get the appropriate ownerUserId const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess(
const createdDirectory = await createDirectory(
context.state.user.id, context.state.user.id,
requestBody.parentPath, requestBody.parentPath,
requestBody.name.trim(), 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 newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath);
const responseBody: ResponseBody = { success: createdDirectory, newDirectories }; const responseBody: ResponseBody = { success: createdDirectory, newDirectories };

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { Directory, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -30,14 +30,22 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
// TODO: Verify user has write access to path and get the appropriate ownerUserId const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess(
const deletedDirectory = await deleteDirectoryOrFile(
context.state.user.id, context.state.user.id,
requestBody.parentPath, requestBody.parentPath,
requestBody.name.trim(), 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 newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath);
const responseBody: ResponseBody = { success: deletedDirectory, newDirectories }; const responseBody: ResponseBody = { success: deletedDirectory, newDirectories };

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { DirectoryFile, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -30,14 +30,30 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
// TODO: Verify user has write access to path/file and get the appropriate ownerUserId let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess(
const deletedFile = await deleteDirectoryOrFile(
context.state.user.id, context.state.user.id,
requestBody.parentPath, requestBody.parentPath,
requestBody.name.trim(), 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 newFiles = await getFiles(context.state.user.id, requestBody.parentPath);
const responseBody: ResponseBody = { success: deletedFile, newFiles }; const responseBody: ResponseBody = { success: deletedFile, newFiles };

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { Directory, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -33,12 +33,26 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); 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( const movedDirectory = await renameDirectoryOrFile(
context.state.user.id, ownerUserId,
requestBody.oldParentPath, oldOwnerParentPath,
requestBody.newParentPath, newOwnerParentPath,
requestBody.name.trim(), requestBody.name.trim(),
requestBody.name.trim(), requestBody.name.trim(),
); );

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { DirectoryFile, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -33,12 +33,35 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
// TODO: Verify user has write access to old and new paths/files and get the appropriate ownerUserIds let { hasWriteAccess: hasOldWriteAccess, ownerUserId, ownerParentPath: oldOwnerParentPath } = await getFileAccess(
const movedFile = await renameDirectoryOrFile(
context.state.user.id, context.state.user.id,
requestBody.oldParentPath, 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, requestBody.newParentPath,
);
if (!hasNewWriteAccess) {
return new Response('Forbidden', { status: 403 });
}
const movedFile = await renameDirectoryOrFile(
ownerUserId,
oldOwnerParentPath,
newOwnerParentPath,
requestBody.name.trim(), requestBody.name.trim(),
requestBody.name.trim(), requestBody.name.trim(),
); );

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { Directory, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -32,12 +32,20 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
// TODO: Verify user has write access to path and get the appropriate ownerUserId const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess(
const movedDirectory = await renameDirectoryOrFile(
context.state.user.id, context.state.user.id,
requestBody.parentPath, 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.oldName.trim(),
requestBody.newName.trim(), requestBody.newName.trim(),
); );

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { DirectoryFile, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -32,12 +32,28 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
// TODO: Verify user has write access to path/file and get the appropriate ownerUserId let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess(
const movedFile = await renameDirectoryOrFile(
context.state.user.id, context.state.user.id,
requestBody.parentPath, 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.oldName.trim(),
requestBody.newName.trim(), requestBody.newName.trim(),
); );

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { DirectoryFile, FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -29,9 +29,25 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); 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); const newFiles = await getFiles(context.state.user.id, parentPath);

View File

@@ -1,7 +1,7 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { FreshContextState } from '/lib/types.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 {} interface Data {}
@@ -31,9 +31,25 @@ export const handler: Handlers<Data, FreshContextState> = {
currentPath = `${currentPath}/`; 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) { if (!fileResult.success) {
return new Response('Not Found', { status: 404 }); return new Response('Not Found', { status: 404 });