wildduck/lib/api/storage.js
2022-11-24 12:26:10 +02:00

343 lines
11 KiB
JavaScript

'use strict';
const Joi = require('joi');
const MongoPaging = require('mongo-cursor-pagination');
const ObjectId = require('mongodb').ObjectId;
const tools = require('../tools');
const roles = require('../roles');
const consts = require('../consts');
const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema } = require('../schemas');
module.exports = (db, server, storageHandler) => {
server.post(
'/users/:user/storage',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
filename: Joi.string().empty('').max(255),
contentType: Joi.string().empty('').max(255),
encoding: Joi.string().empty('').valid('base64'),
content: Joi.binary().max(consts.MAX_ALLOWED_MESSAGE_SIZE).empty('').required(),
sess: sessSchema,
ip: sessIPSchema
});
if (!req.params.content && req.body && (Buffer.isBuffer(req.body) || typeof req.body === 'string')) {
req.params.content = req.body;
}
const result = schema.validate(req.params, {
abortEarly: false,
convert: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).createOwn('storage'));
} else {
req.validate(roles.can(req.role).createAny('storage'));
}
let user = new ObjectId(result.value.user);
let userData;
try {
userData = await db.users.collection('users').findOne(
{
_id: user
},
{
projection: {
address: true
}
}
);
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
if (!userData) {
res.status(404);
return res.json({
error: 'This user does not exist',
code: 'UserNotFound'
});
}
let id = await storageHandler.add(user, result.value);
return res.json({
success: !!id,
id
});
})
);
server.get(
'/users/:user/storage',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
query: Joi.string().trim().empty('').max(255),
limit: Joi.number().default(20).min(1).max(250),
next: nextPageCursorSchema,
previous: previousPageCursorSchema,
page: pageNrSchema,
sess: sessSchema,
ip: sessIPSchema
});
const result = schema.validate(req.params, {
abortEarly: false,
convert: true,
allowUnknown: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).readOwn('storage'));
} else {
req.validate(roles.can(req.role).readAny('storage'));
}
let user = new ObjectId(result.value.user);
let userData;
try {
userData = await db.users.collection('users').findOne(
{
_id: user
},
{
projection: {
address: true
}
}
);
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
if (!userData) {
res.status(404);
return res.json({
error: 'This user does not exist',
code: 'UserNotFound'
});
}
let query = result.value.query;
let limit = result.value.limit;
let page = result.value.page;
let pageNext = result.value.next;
let pagePrevious = result.value.previous;
let filter = (query && {
'metadata.user': user,
filename: {
$regex: tools.escapeRegexStr(query),
$options: ''
}
}) || {
'metadata.user': user
};
let total = await db.gridfs.collection('storage.files').countDocuments(filter);
let opts = {
limit,
query: filter,
paginatedField: 'filename',
sortAscending: true
};
if (pageNext) {
opts.next = pageNext;
} else if ((!page || page > 1) && pagePrevious) {
opts.previous = pagePrevious;
}
let listing;
try {
listing = await MongoPaging.find(db.gridfs.collection('storage.files'), opts);
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
if (!listing.hasPrevious) {
page = 1;
}
let response = {
success: true,
query,
total,
page,
previousCursor: listing.hasPrevious ? listing.previous : false,
nextCursor: listing.hasNext ? listing.next : false,
results: (listing.results || []).map(fileData => ({
id: fileData._id.toString(),
filename: fileData.filename || false,
contentType: fileData.contentType || false,
size: fileData.length,
created: fileData.uploadDate.toISOString(),
md5: fileData.md5
}))
};
return res.json(response);
})
);
server.del(
'/users/:user/storage/:file',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
file: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
});
const result = schema.validate(req.params, {
abortEarly: false,
convert: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
let user = new ObjectId(result.value.user);
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).deleteOwn('storage'));
} else {
req.validate(roles.can(req.role).deleteAny('storage'));
}
let file = new ObjectId(result.value.file);
await storageHandler.delete(user, file);
return res.json({
success: true
});
})
);
server.get(
{ name: 'storagefile', path: '/users/:user/storage/:file' },
tools.responseWrapper(async (req, res) => {
const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
file: Joi.string().hex().lowercase().length(24).required()
});
const result = schema.validate(req.params, {
abortEarly: false,
convert: true
});
if (result.error) {
res.status(400);
return res.json({
error: result.error.message,
code: 'InputValidationError',
details: tools.validationErrors(result)
});
}
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).readOwn('storage'));
} else {
req.validate(roles.can(req.role).readAny('storage'));
}
let user = new ObjectId(result.value.user);
let file = new ObjectId(result.value.file);
let fileData;
try {
fileData = await db.gridfs.collection('storage.files').findOne({
_id: file,
'metadata.user': user
});
} catch (err) {
res.status(500);
return res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
}
if (!fileData) {
res.status(404);
return res.json({
error: 'This file does not exist',
code: 'FileNotFound'
});
}
res.writeHead(200, {
'Content-Type': fileData.contentType || 'application/octet-stream'
});
let stream = storageHandler.gridstore.openDownloadStream(file);
stream.once('error', err => {
try {
res.end(err.message);
} catch (err) {
//ignore
}
});
stream.pipe(res);
})
);
};