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