From fb60cfd05a75c409ca718fd5e08732d212bb0bd1 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Fri, 21 Jul 2023 15:51:31 -0400 Subject: [PATCH] experimental-local-file-access --- experimental-local-file-access/.gitignore | 2 + experimental-local-file-access/package.json | 14 +++++ experimental-local-file-access/readme.md | 47 ++++++++++++++++ experimental-local-file-access/src/index.js | 59 +++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 experimental-local-file-access/.gitignore create mode 100644 experimental-local-file-access/package.json create mode 100644 experimental-local-file-access/readme.md create mode 100644 experimental-local-file-access/src/index.js diff --git a/experimental-local-file-access/.gitignore b/experimental-local-file-access/.gitignore new file mode 100644 index 00000000..25c8fdba --- /dev/null +++ b/experimental-local-file-access/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/experimental-local-file-access/package.json b/experimental-local-file-access/package.json new file mode 100644 index 00000000..36a543b3 --- /dev/null +++ b/experimental-local-file-access/package.json @@ -0,0 +1,14 @@ +{ + "name": "experimental-local-file-access", + "version": "0.0.1", + "description": "experimental service for allowing neurosift to access local files", + "main": "src/index.js", + "author": "Jeremy Magland", + "license": "Apache-2.0", + "dependencies": { + "express": "^4.18.2" + }, + "scripts": { + "start": "node src/index.js" + } +} diff --git a/experimental-local-file-access/readme.md b/experimental-local-file-access/readme.md new file mode 100644 index 00000000..f70ce5d6 --- /dev/null +++ b/experimental-local-file-access/readme.md @@ -0,0 +1,47 @@ +# experimental-local-file-access + +This is a simple server that allows you to access local NWB files using Neurosift. + +## Instructions + +**Prerequisites:** + +* A recent version of NodeJS (tested with v18.16.1) + +**Step 1: Prepare a directory where you will store your .nwb files** + +```bash +export NWB_DIR=/path/to/your/nwb/files +``` + +**Step 2: Install the dependencies and run the server** + +```bash +# cd to this directory +cd experimental-local-file-access + +npm install +npm run start $NWB_DIR +``` + +**Step 3: Open the neurosift web app in your browser and point to an nwb file** + +```bash +https://flatironinstitute.github.io/neurosift/?p=/nwb&url=http://localhost:61762/files/testing.nwb +``` + +This will load the nwb file from `$NWB_DIR/testing.nwb` + +## Using a different port + +By default, the port is 61762. You can change this by setting the `PORT` environment variable in the run command: + +```bash +PORT=12345 npm run start $NWB_DIR +``` + +## Security considerations + +This server will expose the contents of files in the directory you specify to anyone who can access the server. This does not include hidden files (those starting with "."). This is not a problem if you are running the server on your local machine and only accessing it from your local machine. However, if you are running the server on a machine that is accessible from the internet, you should take precautions to ensure that only authorized users can access the server, if you have sensitive files. + +There is also a possibility that websites you visit could get read access to your files. However, the configuration will prevent this from happening unless the website is being served from the allowed domains (flatironinsitute.github.io and localhost:3000). \ No newline at end of file diff --git a/experimental-local-file-access/src/index.js b/experimental-local-file-access/src/index.js new file mode 100644 index 00000000..8f3afa37 --- /dev/null +++ b/experimental-local-file-access/src/index.js @@ -0,0 +1,59 @@ +const express = require('express') +const app = express() +const port = process.env.PORT || 61762 +const dir = process.argv[2] +if (!dir) { + console.error('Please specify a directory.') + process.exit(-1) +} + +// Allow CORS from flatironinstitute.github.io and localhost:3000 +const allowedOrigins = ['https://flatironinstitute.github.io', 'http://localhost:3000'] +app.use((req, resp, next) => { + const origin = req.get('origin') + const allowedOrigin = allowedOrigins.includes(origin) ? origin : undefined + if (allowedOrigin) { + resp.header('Access-Control-Allow-Origin', allowedOrigin) + resp.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept") + } + next() +}) + +// Serve files +app.get('/files/:fileName(*)', async (req, resp) => { + const fileName = req.params.fileName + + // Check if the file is shareable + if (!isShareable(fileName)) { + resp.send(500).send('Access to this file is forbidden.') + return + } + + // Send the file + const options = { + root: dir + } + resp.sendFile(fileName, options, function (err) { + // I think it's important to have an error handler even if it's just this. (not sure though) + }) +}) + +function isShareable(f) { + const bb = f.split('/') + if (bb.includes('..')) { + // don't allow access to parent directories + return false + } + const fileName = bb[bb.length - 1] + if (fileName.startsWith('.')) { + if (!['.zattrs'].includes(fileName)) { + // don't show hidden files (with some exceptions) + return false + } + } + return true +} + +app.listen(port, () => { + console.info(`Serving files in ${dir} on port ${port}.`) +}); \ No newline at end of file