import { api, API_CLIENT } from '../utils/api.ts'; import * as asserts from '@std/assert'; import { USER } from '../models/user.ts'; import { EPHEMERAL_SERVER, get_ephemeral_listen_server, random_username, set_user_permissions } from './helpers.ts'; import { Cookie, getSetCookies } from '@std/http/cookie'; import { generateTotp } from '../utils/totp.ts'; import * as fs from '@std/fs'; import * as path from '@std/path'; import { ensureFile } from '@std/fs'; Deno.test({ name: 'file uploading (home directory)', permissions: { env: true, read: true, write: true, net: true }, sanitizeResources: false, sanitizeOps: false, fn: async () => { let test_server_info: EPHEMERAL_SERVER | null = null; try { console.dir({ SERVERUS_PUT_PATHS_ALLOWED: Deno.env.get('SERVERUS_PUT_PATHS_ALLOWED') }); test_server_info = await get_ephemeral_listen_server(); const client: API_CLIENT = api({ prefix: '/api', hostname: test_server_info.hostname, port: test_server_info.port }); const username = random_username(); const password = 'password'; const user_creation_response: Record = await client.fetch('/users', { method: 'POST', json: { username, password } }); asserts.assert(user_creation_response?.user); asserts.assert(user_creation_response?.session); let cookies: Cookie[] = []; const auth_response: any = await client.fetch('/auth', { method: 'POST', json: { username, password: 'password' }, done: (response) => { cookies = getSetCookies(response.headers); } }); const user: USER | undefined = auth_response.user; asserts.assert(user); asserts.assert(user.id); const session: Record | undefined = auth_response.session; asserts.assert(session); cookies.push({ name: 'totp', value: await generateTotp(session?.secret ?? ''), maxAge: 30, expires: Date.now() + 30_000, path: '/' }); const headers_for_upload_request = new Headers(); for (const cookie of cookies) { headers_for_upload_request.append(`x-${cookie.name}`, cookie.value); } headers_for_upload_request.append( 'cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ') ); const upload_body = new FormData(); upload_body.append( 'file', new File(['this is the test content'], 'test_uploading_to_home_dir.txt') ); const upload_response = await fetch( `http://${test_server_info.hostname}:${test_server_info.port}/files/users/${user.id}/test_uploading_to_home_dir.txt`, { method: 'PUT', headers: headers_for_upload_request, body: upload_body } ); asserts.assert(upload_response.ok); const upload_response_body = await upload_response.text(); asserts.assert(upload_response_body); const get_response = await fetch( `http://${test_server_info.hostname}:${test_server_info.port}/files/users/${user.id}/test_uploading_to_home_dir.txt` ); asserts.assert(get_response.ok); asserts.assert(get_response.body); const local_download_path = path.join(Deno.cwd(), 'files', 'users', user.id, 'test_uploading_to_home_dir.txt-downloaded'); await ensureFile(local_download_path); const file = await Deno.open(local_download_path, { truncate: true, write: true }); await get_response.body.pipeTo(file.writable); const download_content = await Deno.readTextFile(local_download_path); asserts.assert(download_content); asserts.assertEquals(download_content, 'this is the test content'); await Deno.remove(local_download_path); asserts.assert(!fs.existsSync(local_download_path)); const uploaded_to_homedir_path = path.join(Deno.cwd(), 'files', 'users', user.id, 'test_uploading_to_home_dir.txt'); await Deno.remove(uploaded_to_homedir_path); asserts.assert(!fs.existsSync(uploaded_to_homedir_path)); let dir = path.dirname(uploaded_to_homedir_path); do { if (dir.endsWith('files')) { break; } const files = Deno.readDirSync(dir); let has_files = false; for (const _ of files) { has_files = true; break; } if (has_files) { dir = ''; break; } await Deno.remove(dir); dir = path.dirname(dir); } while (dir.length); } finally { if (test_server_info) { await test_server_info?.server?.stop(); } } } }); Deno.test({ name: 'file uploading (outside home directory)', permissions: { env: true, read: true, write: true, net: true }, sanitizeResources: false, sanitizeOps: false, fn: async () => { let test_server_info: EPHEMERAL_SERVER | null = null; try { test_server_info = await get_ephemeral_listen_server(); const client: API_CLIENT = api({ prefix: '/api', hostname: test_server_info.hostname, port: test_server_info.port }); const username = random_username(); const password = 'password'; const user_creation_response: Record = await client.fetch('/users', { method: 'POST', json: { username, password } }); asserts.assert(user_creation_response?.user); asserts.assert(user_creation_response?.session); let cookies: Cookie[] = []; const auth_response: any = await client.fetch('/auth', { method: 'POST', json: { username, password: 'password' }, done: (response) => { cookies = getSetCookies(response.headers); } }); const user: USER | undefined = auth_response.user; asserts.assert(user); asserts.assert(user.id); const session: Record | undefined = auth_response.session; asserts.assert(session); cookies.push({ name: 'totp', value: await generateTotp(session?.secret ?? ''), maxAge: 30, expires: Date.now() + 30_000, path: '/' }); const headers_for_upload_request = new Headers(); for (const cookie of cookies) { headers_for_upload_request.append(`x-${cookie.name}`, cookie.value); } headers_for_upload_request.append( 'cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ') ); const upload_body = new FormData(); upload_body.append( 'file', new File(['this is the root dir test content'], 'test_uploading_to_root_dir.txt') ); const disallowed_upload_response = await fetch( `http://${test_server_info.hostname}:${test_server_info.port}/files/test_uploading_to_root_dir.txt`, { method: 'PUT', headers: headers_for_upload_request, body: upload_body } ); asserts.assert(!disallowed_upload_response.ok); await disallowed_upload_response.text(); await set_user_permissions(client, user, session, [...user.permissions, 'files.write.all']); const allowed_upload_response = await fetch( `http://${test_server_info.hostname}:${test_server_info.port}/files/test_uploading_to_root_dir.txt`, { method: 'PUT', headers: headers_for_upload_request, body: upload_body } ); asserts.assert(allowed_upload_response.ok); await allowed_upload_response.text(); const get_response = await fetch( `http://${test_server_info.hostname}:${test_server_info.port}/files/test_uploading_to_root_dir.txt` ); asserts.assert(get_response.ok); asserts.assert(get_response.body); const local_download_path = path.join(Deno.cwd(), 'files', 'test_uploading_to_root_dir.txt-downloaded'); await ensureFile(local_download_path); const file = await Deno.open(local_download_path, { truncate: true, write: true }); await get_response.body.pipeTo(file.writable); const download_content = await Deno.readTextFile(local_download_path); asserts.assert(download_content); asserts.assertEquals(download_content, 'this is the root dir test content'); await Deno.remove(local_download_path); asserts.assert(!fs.existsSync(local_download_path)); const uploaded_to_root_dir_path = path.join(Deno.cwd(), 'files', 'test_uploading_to_root_dir.txt'); await Deno.remove(uploaded_to_root_dir_path); asserts.assert(!fs.existsSync(uploaded_to_root_dir_path)); let dir = path.dirname(uploaded_to_root_dir_path); do { if (dir.endsWith('files')) { break; } const files = Deno.readDirSync(dir); let has_files = false; for (const _ of files) { has_files = true; break; } if (has_files) { dir = ''; break; } await Deno.remove(dir); dir = path.dirname(dir); } while (dir.length); } finally { if (test_server_info) { await test_server_info?.server?.stop(); } } } });