Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

env variable to define a custom sid length #283

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -86,6 +86,8 @@ You can:
See `config.dev.js`
* Define environment Variables like `PSITRANSFER_UPLOAD_DIR` to set the upload directory
* To secure your PsiTransfer if exposed to the internet from unwanted, non authorized uploads use the `PSITRANSFER_UPLOAD_PASS` environment variable
* Use PSITRANSFER_RANDOM_DOWNLOAD_SID_LENGTH to define the length of the downloadlink
Beware, shorten the link can result in adding files to an existing bucket

### Customization

Expand Down
30 changes: 20 additions & 10 deletions app/src/Upload/store/upload.js
Expand Up @@ -16,24 +16,29 @@ export function humanFileSize(fileSizeInBytes) {
let onOnlineHandler = null;
let onOnlineHandlerAttached = false;

function getSid() {
function getSid(state) {
// support setting an explicit SID by location search param
const match = document.location.search.match(/sid=([^&]+)/);
if (match) {
return match[1];
} else {
return md5(uuid()).toString().substr(0, 12);
return getRandomId(state);
}
}

function getRandomId(state) {
return md5(uuid()).toString().substr(0, state.config.randomDownloadSidLength);

}

export default {
namespaced: true,

state: {
retention: null,
password: '',
files: [],
sid: getSid(),
sid: "",
uploadURI: (window.PSITRANSFER_UPLOAD_PATH || '/') + 'files',
},

Expand All @@ -51,8 +56,8 @@ export default {
},
bucketSizeError: (state, getters, rootState) => {
const maxBucketSize = rootState.config && rootState.config.maxBucketSize;
if(!maxBucketSize) return false;
if(getters.bucketSize > maxBucketSize) {
if (!maxBucketSize) return false;
if (getters.bucketSize > maxBucketSize) {
return rootState.lang.bucketSizeExceed
.replace('%%', humanFileSize(getters.bucketSize))
.replace('%%', humanFileSize(maxBucketSize));
Expand Down Expand Up @@ -83,12 +88,17 @@ export default {
NEW_SESSION(state) {
state.password = '';
state.files.splice(0, state.files.length);
state.sid = md5(uuid()).toString().substr(0, 12);
state.sid = getRandomId(state);
},
SET_SID(state, payload) {
state.sid = payload;
}
},

actions: {
addFiles({ commit, state, rootState }, files) {
commit('SET_SID', getSid(rootState));

if (state.disabled) return;
for (let i = 0; i < files.length; i++) {
let error = false;
Expand All @@ -113,7 +123,7 @@ export default {
}
},

removeFile({commit, state}, file) {
removeFile({ commit, state }, file) {
commit('REMOVE_FILE', file);
},

Expand All @@ -122,7 +132,7 @@ export default {
commit('ERROR', '', { root: true });

if (onOnlineHandler === null) {
onOnlineHandler = function() {
onOnlineHandler = function () {
onOnlineHandlerAttached = false;
commit('ERROR', false, { root: true });
dispatch('upload');
Expand Down Expand Up @@ -156,9 +166,9 @@ export default {
endpoint: state.uploadURI,
storeFingerprintForResuming: false,
retryDelays: null,
onAfterResponse: function(req, res) {
onAfterResponse: function (req, res) {
// Remember uploadUrl for resuming
if(req.getMethod() === 'POST'
if (req.getMethod() === 'POST'
&& req.getURL() === this.endpoint
&& res.getStatus() === 201
) {
Expand Down
3 changes: 2 additions & 1 deletion config.dev.js
Expand Up @@ -18,7 +18,8 @@ module.exports = {
"uploadAppPath": '/',
// "maxFileSize": Math.pow(2, 20) * 15,
// "maxBucketSize": Math.pow(2, 20) * 20,
"mailFrom": "PsiTransfer <psitransfer@psi.cx>"
"mailFrom": "PsiTransfer <psitransfer@psi.cx>",
// "sslKeyFile": './tmp/cert.key',
// "sslCertFile": './tmp/cert.pem',
"randomDownloadSidLength": 12,
};
7 changes: 4 additions & 3 deletions config.js
Expand Up @@ -60,10 +60,11 @@ const config = {
"maxFileSize": null, // Math.pow(2, 30) * 2, // 2GB
"maxBucketSize": null, // Math.pow(2, 30) * 2, // 10GB
"plugins": ['file-downloaded-webhook', 'file-uploaded-webhook'],
"randomDownloadSidLength": 12,
};

// Load NODE_ENV specific config
const envConfFile = path.resolve(__dirname, `config.${ process.env.NODE_ENV }.js`);
const envConfFile = path.resolve(__dirname, `config.${process.env.NODE_ENV}.js`);
if (process.env.NODE_ENV && fsp.existsSync(envConfFile)) {
Object.assign(config, require(envConfFile));
}
Expand Down Expand Up @@ -92,14 +93,14 @@ config.uploadAppPath = config.baseUrl.substr(0, config.baseUrl.length - 1) + con

// Load language files
config.languages = {
[config.defaultLanguage]: require(`./lang/${ config.defaultLanguage }`) // default language
[config.defaultLanguage]: require(`./lang/${config.defaultLanguage}`) // default language
};
fs.readdirSync(path.resolve(__dirname, 'lang')).forEach(lang => {
lang = lang.replace('.js', '');
if (lang === config.defaultLanguage) return;
config.languages[lang] = {
...config.languages[config.defaultLanguage],
...require(`./lang/${ lang }`)
...require(`./lang/${lang}`)
};
});

Expand Down
61 changes: 31 additions & 30 deletions lib/endpoints.js
Expand Up @@ -43,16 +43,16 @@ if (config.accessLog) {

if (config.forceHttps) {
app.enable('trust proxy');
app.use(function(req, res, next) {
app.use(function (req, res, next) {
if (req.secure) return next();
const target = config.forceHttps === 'true' ? 'https://' + req.headers.host : config.forceHttps;
res.redirect(target + req.url);
});
}

// Static files
app.use(`${ config.baseUrl }app`, express.static(path.join(__dirname, '../public/app')));
app.use(`${ config.baseUrl }assets`, express.static(path.join(__dirname, '../public/assets')));
app.use(`${config.baseUrl}app`, express.static(path.join(__dirname, '../public/app')));
app.use(`${config.baseUrl}assets`, express.static(path.join(__dirname, '../public/assets')));

// Resolve language
app.use((req, res, next) => {
Expand All @@ -62,7 +62,7 @@ app.use((req, res, next) => {
});

// robots.txt
app.get(`${ config.baseUrl }robots.txt`, (req, res) => {
app.get(`${config.baseUrl}robots.txt`, (req, res) => {
res.sendFile(path.join(__dirname, '../public/robots.txt'));
});

Expand All @@ -82,13 +82,13 @@ app.get(config.uploadAppPath, (req, res) => {
});

// Return translations
app.get(`${ config.baseUrl }lang.json`, (req, res) => {
app.get(`${config.baseUrl}lang.json`, (req, res) => {
eventBus.emit('getLang', req.translations);
res.json(req.translations);
});

// Config
app.get(`${ config.baseUrl }config.json`, (req, res) => {
app.get(`${config.baseUrl}config.json`, (req, res) => {
// Upload password protection
if (config.uploadPass) {
const bfTimeout = 200;
Expand All @@ -109,19 +109,20 @@ app.get(`${ config.baseUrl }config.json`, (req, res) => {
requireBucketPassword: config.requireBucketPassword,
maxFileSize: config.maxFileSize,
maxBucketSize: config.maxBucketSize,
randomDownloadSidLength: config.randomDownloadSidLength
};

eventBus.emit('getFrontendConfig', frontendConfig);

res.json(frontendConfig);
});

app.get(`${ config.baseUrl }admin`, (req, res, next) => {
app.get(`${config.baseUrl}admin`, (req, res, next) => {
if (!config.adminPass) return next();
res.send(adminPage({ ...pugVars, lang: req.translations }));
});

app.get(`${ config.baseUrl }admin/data.json`, (req, res, next) => {
app.get(`${config.baseUrl}admin/data.json`, (req, res, next) => {
if (!config.adminPass) return next();

const bfTimeout = 500;
Expand Down Expand Up @@ -153,21 +154,21 @@ app.get(`${ config.baseUrl }admin/data.json`, (req, res, next) => {


// List files / Download App
app.get(`${ config.baseUrl }:sid`, (req, res, next) => {
app.get(`${config.baseUrl}:sid`, (req, res, next) => {
if (req.url.endsWith('.json')) {
const sid = req.params.sid.substr(0, req.params.sid.length - 5);
if (!db.get(sid)) return res.status(404).end();

const downloadPassword = req.get('x-download-pass');
const items = db.get(sid).map(item => ({
...item,
url: `${ config.baseUrl }files/${ sid }++${ item.key }`
url: `${config.baseUrl}files/${sid}++${item.key}`
}));

res.header('Cache-control', 'private, max-age=0, no-cache, no-store, must-revalidate');

// Currently, every item in a bucket must have the same password
if(items.some(item => item.metadata.password && item.metadata.password !== downloadPassword)) {
if (items.some(item => item.metadata.password && item.metadata.password !== downloadPassword)) {
setTimeout(() => res.status(401).send('Unauthorized'), 500);
return;
}
Expand All @@ -186,7 +187,7 @@ app.get(`${ config.baseUrl }:sid`, (req, res, next) => {


// Download files
app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
app.get(`${config.baseUrl}files/:fid`, async (req, res, next) => {
// let tusboy handle HEAD requests with Tus Header
if (req.method === 'HEAD' && req.get('Tus-Resumable')) return next();

Expand All @@ -198,11 +199,11 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
const bucket = db.get(sid);

if (!bucket) return res.status(404).send(errorPage({
...pugVars,
error: 'Download bucket not found.',
lang: req.translations,
uploadAppPath: config.uploadAppPath || config.baseUrl,
}));
...pugVars,
error: 'Download bucket not found.',
lang: req.translations,
uploadAppPath: config.uploadAppPath || config.baseUrl,
}));

if (req.params.fid !== sid + '++' + MD5(bucket.map(f => f.key).join()).toString() + '.' + format) {
res.status(404).send(errorPage({
Expand All @@ -213,10 +214,10 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
}));
return;
}
debug(`Download Bucket ${ sid }`);
debug(`Download Bucket ${sid}`);

const filename = `${ sid }.${ format }`;
res.header('Content-Disposition', `attachment; filename="${ filename }"`);
const filename = `${sid}.${format}`;
res.header('Content-Disposition', `attachment; filename="${filename}"`);

try {
res.on('finish', async () => {
Expand All @@ -241,10 +242,10 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
console.error(e);
}

if(format === 'zip') {
if (format === 'zip') {
res.header('ContentType', 'application/zip');
const archive = archiver('zip');
archive.on('error', function(err) {
archive.on('error', function (err) {
console.error(err);
});
archive.pipe(res);
Expand All @@ -269,7 +270,7 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
const entry = pack.entry({ name: info.metadata.name, size: info.size });
readStream.on('error', reject);
entry.on('error', reject);
entry.on('finish',resolve);
entry.on('finish', resolve);
readStream.pipe(entry);
});
}
Expand All @@ -280,7 +281,7 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
}

// Download single file
debug(`Download ${ req.params.fid }`);
debug(`Download ${req.params.fid}`);
try {
const info = await store.info(req.params.fid); // throws on 404
res.download(store.getFilename(req.params.fid), info.metadata.name);
Expand Down Expand Up @@ -313,8 +314,8 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {


// Upload file
app.use(`${ config.uploadAppPath }files`,
function(req, res, next) {
app.use(`${config.uploadAppPath}files`,
function (req, res, next) {
// Upload password protection
if (config.uploadPass) {
const bfTimeout = 500;
Expand All @@ -340,7 +341,7 @@ app.use(`${ config.uploadAppPath }files`,
assert(meta.sid, 'tus meta prop missing: sid');
assert(meta.retention, 'tus meta prop missing: retention');
assert(Object.keys(config.retentions).indexOf(meta.retention) >= 0,
`invalid tus meta prop retention. Value ${ meta.retention } not in [${ Object.keys(config.retentions).join(',') }]`);
`invalid tus meta prop retention. Value ${meta.retention} not in [${Object.keys(config.retentions).join(',')}]`);

const uploadLength = req.get('Upload-Length');
assert(uploadLength, 'missing Upload-Length header');
Expand All @@ -353,11 +354,11 @@ app.use(`${ config.uploadAppPath }files`,
if (config.maxFileSize && config.maxFileSize < +uploadLength) {
return res
.status(413)
.json({ message: `File exceeds maximum upload size ${ config.maxFileSize }.` });
.json({ message: `File exceeds maximum upload size ${config.maxFileSize}.` });
} else if (config.maxBucketSize && db.bucketSize(meta.sid) + +uploadLength > config.maxBucketSize) {
return res
.status(413)
.json({ message: `Bucket exceeds maximum upload size ${ config.maxBucketSize }.` });
.json({ message: `Bucket exceeds maximum upload size ${config.maxBucketSize}.` });
}

// store changed metadata for tusboy
Expand Down Expand Up @@ -385,7 +386,7 @@ app.use(`${ config.uploadAppPath }files`,
maxUploadLength: config.maxFileSize || Infinity,
afterComplete: (req, upload, fid) => {
db.add(upload.metadata.sid, upload.metadata.key, upload);
debug(`Completed upload ${ fid }, size=${ upload.size } name=${ upload.metadata.name }`);
debug(`Completed upload ${fid}, size=${upload.size} name=${upload.metadata.name}`);

eventBus.emit('fileUploaded', upload);
},
Expand Down