mirror of
https://github.com/Foltik/Shimapan
synced 2025-03-27 05:23:40 -04:00
Fix upload size bug in upload.js and add more tests
This commit is contained in:
parent
827c4cdbfa
commit
eda74de1de
@ -1,17 +1,18 @@
|
|||||||
var express = require('express');
|
const express = require('express');
|
||||||
var router = express.Router();
|
const router = express.Router();
|
||||||
|
const config = require('config');
|
||||||
|
|
||||||
var User = require('../models/User.js');
|
const User = require('../models/User.js');
|
||||||
var Upload = require('../models/Upload.js');
|
const Upload = require('../models/Upload.js');
|
||||||
var Key = require('../models/Key.js');
|
const Key = require('../models/Key.js');
|
||||||
|
|
||||||
var multer = require('multer');
|
const multer = require('multer');
|
||||||
var dest = multer({dest: 'uploads/'});
|
const fileUpload = multer({dest: config.uploadPath}).single('file');
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
|
|
||||||
const requireAuth = require('../util/requireAuth').requireAuth;
|
const requireAuth = require('../util/requireAuth').requireAuth;
|
||||||
const wrap = require('../util/wrap.js').wrap;
|
const wrap = require('../util/wrap.js').wrap;
|
||||||
|
|
||||||
|
|
||||||
const generatedIdExists = async id =>
|
const generatedIdExists = async id =>
|
||||||
await Upload.countDocuments({id: id}) === 1;
|
await Upload.countDocuments({id: id}) === 1;
|
||||||
|
|
||||||
@ -37,21 +38,21 @@ const updateStats = async req =>
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
router.post('/', requireAuth('file.upload'), dest.single('file'), wrap(async (req, res, next) => {
|
router.post('/', requireAuth('file.upload'), fileUpload, wrap(async (req, res, next) => {
|
||||||
if (!req.file)
|
if (!req.file)
|
||||||
return res.status(400).json({message: 'No file specified.'});
|
return res.status(400).json({message: 'No file specified.'});
|
||||||
|
|
||||||
// Max file size is 128 MiB
|
// Max file size is 128 MiB
|
||||||
if (req.file.size > 1024 * 1024 * 128)
|
if (req.file.size > 1024 * 1024 * 128) {
|
||||||
|
await fsPromises.unlink(req.file.path);
|
||||||
return res.status(413).json({message: 'File too large.'});
|
return res.status(413).json({message: 'File too large.'});
|
||||||
|
}
|
||||||
|
|
||||||
const upload = {
|
const upload = {
|
||||||
name: req.file.originalname,
|
|
||||||
id: await generateId(),
|
id: await generateId(),
|
||||||
uploader: req.authUser,
|
uploader: req.authUser,
|
||||||
uploaderKey: req.authKey,
|
uploaderKey: req.authKey,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
mime: req.file.mimetype,
|
|
||||||
file: req.file
|
file: req.file
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dbHost": "mongodb://localhost:27017/shimapan",
|
"dbHost": "mongodb://localhost:27017/shimapan",
|
||||||
|
"uploadPath": "uploads",
|
||||||
"httpLogLevel": "dev"
|
"httpLogLevel": "dev"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dbHost": "mongodb://localhost:27017/shimapan",
|
"dbHost": "mongodb://localhost:27017/shimapan",
|
||||||
|
"uploadPath": "uploads",
|
||||||
"httpLogLevel": "dev"
|
"httpLogLevel": "dev"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dbHost": "mongodb://localhost:27017/shimapan-test",
|
"dbHost": "mongodb://localhost:27017/shimapan-test",
|
||||||
|
"uploadPath": "uploads-test",
|
||||||
"httpLogLevel": "dev"
|
"httpLogLevel": "dev"
|
||||||
}
|
}
|
57
test/api.js
57
test/api.js
@ -7,6 +7,7 @@ const should = chai.should();
|
|||||||
const User = require('../app/models/User.js');
|
const User = require('../app/models/User.js');
|
||||||
const Invite = require('../app/models/Invite.js');
|
const Invite = require('../app/models/Invite.js');
|
||||||
const Upload = require('../app/models/Upload.js');
|
const Upload = require('../app/models/Upload.js');
|
||||||
|
const Key = require('../app/models/Key.js');
|
||||||
|
|
||||||
const util = require('./testUtil.js');
|
const util = require('./testUtil.js');
|
||||||
const canonicalize = require('../app/util/canonicalize').canonicalize;
|
const canonicalize = require('../app/util/canonicalize').canonicalize;
|
||||||
@ -26,7 +27,7 @@ after(() => {
|
|||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Users', function() {
|
describe('Accounts', function() {
|
||||||
beforeEach(async () => util.clearDatabase());
|
beforeEach(async () => util.clearDatabase());
|
||||||
|
|
||||||
describe('/POST register', () => {
|
describe('/POST register', () => {
|
||||||
@ -41,10 +42,10 @@ describe('Users', function() {
|
|||||||
res.body.should.have.property('message').eql('Registration successful.');
|
res.body.should.have.property('message').eql('Registration successful.');
|
||||||
|
|
||||||
const userCount = await User.countDocuments({username: user.username});
|
const userCount = await User.countDocuments({username: user.username});
|
||||||
userCount.should.eql(1);
|
userCount.should.equal(1);
|
||||||
|
|
||||||
const inviteCount = await Invite.countDocuments({code: user.invite, recipient: canonicalize(user.username)});
|
const inviteCount = await Invite.countDocuments({code: user.invite, recipient: canonicalize(user.username)});
|
||||||
inviteCount.should.eql(1);
|
inviteCount.should.equal(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('MUST register a valid user with a valid invite', async () =>
|
it('MUST register a valid user with a valid invite', async () =>
|
||||||
@ -69,6 +70,9 @@ describe('Users', function() {
|
|||||||
res.should.have.status(422);
|
res.should.have.status(422);
|
||||||
res.body.should.be.a('object');
|
res.body.should.be.a('object');
|
||||||
res.body.should.have.property('message').eql(message);
|
res.body.should.have.property('message').eql(message);
|
||||||
|
|
||||||
|
const inviteCount = await Invite.countDocuments({code: user.invite, recipient: canonicalize(user.username)});
|
||||||
|
inviteCount.should.equal(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('MUST NOT register a nonexistant invite', async () =>
|
it('MUST NOT register a nonexistant invite', async () =>
|
||||||
@ -90,7 +94,10 @@ describe('Users', function() {
|
|||||||
const res = await util.registerUser(user, agent);
|
const res = await util.registerUser(user, agent);
|
||||||
res.should.have.status(422);
|
res.should.have.status(422);
|
||||||
res.body.should.be.a('object');
|
res.body.should.be.a('object');
|
||||||
res.body.should.have.property('message').eql(message);
|
res.body.should.have.property('message').equal(message);
|
||||||
|
|
||||||
|
const inviteCount = await Invite.countDocuments({code: user.invite, recipient: canonicalize(user.username)});
|
||||||
|
inviteCount.should.equal(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('MUST NOT register a duplicate username', async () => {
|
it('MUST NOT register a duplicate username', async () => {
|
||||||
@ -186,19 +193,51 @@ describe('Uploads', () => {
|
|||||||
beforeEach(async () => util.clearDatabase());
|
beforeEach(async () => util.clearDatabase());
|
||||||
|
|
||||||
describe('/POST upload', () => {
|
describe('/POST upload', () => {
|
||||||
async function verifySuccessfulUpload(file) {
|
async function verifySuccessfulUpload(file, user) {
|
||||||
|
// Get file stats beforehand
|
||||||
|
const [fileHash, fileSize] = await Promise.all([util.fileHash(file), util.fileSize(file)]);
|
||||||
|
|
||||||
|
// Get the user stats beforehand
|
||||||
|
const userBefore = await User.findOne({canonicalname: user}, {_id: 0, uploadCount: 1, uploadSize: 1});
|
||||||
|
|
||||||
|
// Submit the upload and verify the result
|
||||||
const res = await util.upload(file, agent);
|
const res = await util.upload(file, agent);
|
||||||
res.should.have.status(200);
|
res.should.have.status(200);
|
||||||
res.body.should.be.a('object');
|
res.body.should.be.a('object');
|
||||||
res.body.should.have.property('url');
|
res.body.should.have.property('url');
|
||||||
res.body.should.have.property('id').match(/^[a-z]{6}$/);
|
res.body.should.have.property('id').match(/^[a-z]{6}$/);
|
||||||
|
|
||||||
|
// Find the uploaded file in the database
|
||||||
|
const upload = await Upload.findOne({id: res.body.id}, {_id: 0, id: 1, file: 1});
|
||||||
|
const uploadFile = upload.file.path;
|
||||||
|
upload.should.be.a('object');
|
||||||
|
upload.id.should.equal(res.body.id);
|
||||||
|
|
||||||
|
// Verify the uploaded file is the same as the file now on disk
|
||||||
|
const [uploadHash, uploadSize] = await Promise.all([util.fileHash(uploadFile), util.fileSize(uploadFile)]);
|
||||||
|
uploadHash.should.equal(fileHash);
|
||||||
|
uploadSize.should.equal(fileSize);
|
||||||
|
|
||||||
|
// Verify the user's stats have been updated correctly
|
||||||
|
const userAfter = await User.findOne({canonicalname: user}, {_id: 0, uploadCount: 1, uploadSize: 1});
|
||||||
|
userAfter.uploadCount.should.equal(userBefore.uploadCount + 1);
|
||||||
|
userAfter.uploadSize.should.equal(userBefore.uploadSize + fileSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyFailedUpload(file, status, message) {
|
async function verifyFailedUpload(file, status, message) {
|
||||||
|
const fileCountBefore = await util.directoryFileCount('uploads');
|
||||||
|
const uploadCountBefore = await Upload.countDocuments({});
|
||||||
|
|
||||||
const res = await util.upload(file, agent);
|
const res = await util.upload(file, agent);
|
||||||
res.should.have.status(status);
|
res.should.have.status(status);
|
||||||
res.body.should.be.a('object');
|
res.body.should.be.a('object');
|
||||||
res.body.should.have.property('message').equal(message);
|
res.body.should.have.property('message').equal(message);
|
||||||
|
|
||||||
|
const fileCountAfter = await util.directoryFileCount('uploads');
|
||||||
|
fileCountAfter.should.equal(fileCountBefore, 'File should not have been written to disk');
|
||||||
|
|
||||||
|
const uploadCountAfter = await Upload.countDocuments({});
|
||||||
|
uploadCountAfter.should.equal(uploadCountBefore, 'No uploads should have been written to the database');
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('0 Valid Request', () => {
|
describe('0 Valid Request', () => {
|
||||||
@ -208,11 +247,11 @@ describe('Uploads', () => {
|
|||||||
util.createTestFile(2048, 'test.bin')
|
util.createTestFile(2048, 'test.bin')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await verifySuccessfulUpload('test.bin');
|
await verifySuccessfulUpload('test.bin', 'user');
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
util.logout(agent),
|
util.logout(agent),
|
||||||
util.deleteTestFile('test.bin')
|
util.deleteFile('test.bin')
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -233,7 +272,7 @@ describe('Uploads', () => {
|
|||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
util.logout(agent),
|
util.logout(agent),
|
||||||
util.deleteTestFile('test.bin')
|
util.deleteFile('test.bin')
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -249,7 +288,7 @@ describe('Uploads', () => {
|
|||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
util.logout(agent),
|
util.logout(agent),
|
||||||
util.deleteTestFile('large.bin')
|
util.deleteFile('large.bin')
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,11 +11,12 @@ const Key = require('../app/models/Key.js');
|
|||||||
|
|
||||||
const Buffer = require('buffer').Buffer;
|
const Buffer = require('buffer').Buffer;
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs');
|
||||||
|
const fsPromises = fs.promises;
|
||||||
|
|
||||||
//---------------- DATABASE UTIL ----------------//
|
//---------------- DATABASE UTIL ----------------//
|
||||||
|
|
||||||
exports.clearDatabase = async () =>
|
exports.clearDatabase = () =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
User.remove({}),
|
User.remove({}),
|
||||||
Invite.remove({}),
|
Invite.remove({}),
|
||||||
@ -25,7 +26,7 @@ exports.clearDatabase = async () =>
|
|||||||
|
|
||||||
//---------------- API ROUTES ----------------//
|
//---------------- API ROUTES ----------------//
|
||||||
|
|
||||||
exports.login = async (credentials, agent) =>
|
exports.login = (credentials, agent) =>
|
||||||
agent
|
agent
|
||||||
.post('/api/auth/login')
|
.post('/api/auth/login')
|
||||||
.send(credentials);
|
.send(credentials);
|
||||||
@ -34,25 +35,25 @@ exports.logout = agent =>
|
|||||||
agent
|
agent
|
||||||
.post('/api/auth/logout');
|
.post('/api/auth/logout');
|
||||||
|
|
||||||
exports.createInvite = async (invite) =>
|
exports.createInvite = (invite) =>
|
||||||
Invite.create(invite);
|
Invite.create(invite);
|
||||||
|
|
||||||
exports.registerUser = async (user, agent) =>
|
exports.registerUser = (user, agent) =>
|
||||||
agent
|
agent
|
||||||
.post('/api/auth/register')
|
.post('/api/auth/register')
|
||||||
.send(user);
|
.send(user);
|
||||||
|
|
||||||
exports.whoami = async (agent) =>
|
exports.whoami = (agent) =>
|
||||||
agent
|
agent
|
||||||
.get('/api/auth/whoami')
|
.get('/api/auth/whoami')
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
//---------------- TEST ENTRY CREATION ----------------//
|
//---------------- TEST ENTRY CREATION ----------------//
|
||||||
|
|
||||||
exports.createTestInvite = async () =>
|
exports.createTestInvite = () =>
|
||||||
exports.createInvite({code: 'code', scope: ['file.upload']});
|
exports.createInvite({code: 'code', scope: ['file.upload']});
|
||||||
|
|
||||||
exports.createTestInvites = async (n) =>
|
exports.createTestInvites = (n) =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
Array.from(new Array(n), (val, index) => 'code' + index)
|
Array.from(new Array(n), (val, index) => 'code' + index)
|
||||||
.map(code => exports.createInvite({code: code}))
|
.map(code => exports.createInvite({code: code}))
|
||||||
@ -65,14 +66,34 @@ exports.createTestUser = async agent => {
|
|||||||
|
|
||||||
exports.createTestSession = async agent => {
|
exports.createTestSession = async agent => {
|
||||||
await exports.createTestUser(agent);
|
await exports.createTestUser(agent);
|
||||||
await exports.login({username: 'user', password: 'pass'}, agent);
|
return exports.login({username: 'user', password: 'pass'}, agent);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.createTestFile = async (size, name) =>
|
exports.createTestFile = (size, name) =>
|
||||||
fs.writeFile(name, Buffer.allocUnsafe(size));
|
fsPromises.writeFile(name, Buffer.allocUnsafe(size));
|
||||||
|
|
||||||
exports.deleteTestFile = async name =>
|
//---------------- FILESYSTEM ----------------//
|
||||||
fs.unlink(name);
|
|
||||||
|
exports.deleteFile = file =>
|
||||||
|
fsPromises.unlink(file);
|
||||||
|
|
||||||
|
exports.fileExists = file =>
|
||||||
|
fsPromises.access(file, fs.constants.R_OK);
|
||||||
|
|
||||||
|
exports.fileSize = async file =>
|
||||||
|
(await fsPromises.stat(file)).size;
|
||||||
|
|
||||||
|
exports.fileHash = file =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const hash = crypto.createHash('MD5');
|
||||||
|
fs.createReadStream(file)
|
||||||
|
.on('error', reject)
|
||||||
|
.on('data', chunk => hash.update(chunk))
|
||||||
|
.on('end', () => resolve(hash.digest('hex')));
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.directoryFileCount = async dir =>
|
||||||
|
(await fsPromises.readdir(dir)).length;
|
||||||
|
|
||||||
//---------------- UPLOADS ----------------//
|
//---------------- UPLOADS ----------------//
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user