Implemented changing code ownership
Refactored admin API
This commit is contained in:
parent
88beaa6f7d
commit
d6f553e99f
20
emails/movednotification/html.ejs
Normal file
20
emails/movednotification/html.ejs
Normal file
@ -0,0 +1,20 @@
|
||||
<% let expirationDate = null; %>
|
||||
<p>Hello <%=user.name%>,</p>
|
||||
<p>The following files have been assigned from <%=prevUser.name%> to you<%=prevUser.userId !== assignUser.userId ? ' by ' + assignUser.name : ''%>:</p>
|
||||
<ul>
|
||||
<% uploads.forEach((upload) => { %>
|
||||
<% upload.files.forEach((file) => { %>
|
||||
<li><%=file.name%> (<%=humanFileSize(file.size, true)%>)</li>
|
||||
<%
|
||||
});
|
||||
|
||||
if (upload.expirationDate !== null && (expirationDate === null || upload.expirationDate < expirationDate))
|
||||
expirationDate = upload.expirationDate;
|
||||
%>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% if (expirationDate !== null) { %>
|
||||
These files will be automatically deleted after <%=expirationDate.toLocaleString('en-US')%>
|
||||
<% } %>
|
||||
<p>You can download these files by logging in to <a href="<%=adminUrl%>"><%=adminUrl%></a></p>
|
||||
<p>Cheers,<br />Recv</p>
|
1
emails/movednotification/subject.ejs
Normal file
1
emails/movednotification/subject.ejs
Normal file
@ -0,0 +1 @@
|
||||
📄 <%=fileCount%> file<%=fileCount != 1 ? 's have' : ' has'%> been assigned to you
|
@ -1,36 +1,12 @@
|
||||
<%
|
||||
function humanFileSize(bytes, si)
|
||||
{
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if(Math.abs(bytes) < thresh)
|
||||
{
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
var units = si
|
||||
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||
var u = -1;
|
||||
|
||||
do
|
||||
{
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
}
|
||||
while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
%>
|
||||
<p>Hello <%= user.name %>,</p>
|
||||
<p>The following files have just been uploaded:</p>
|
||||
<p>Hello <%=user.name%>,</p>
|
||||
<p>The following files have been uploaded:</p>
|
||||
<ul>
|
||||
<% upload.files.forEach((file) => { %>
|
||||
<li><%= file.name %> (<%= humanFileSize(file.size, true) %>)</li>
|
||||
<li><%=file.name%> (<%=humanFileSize(file.size, true)%>)</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% if (upload.expirationDate !== null) { %>
|
||||
These files will be automatically deleted after <%=upload.expirationDate.toLocaleString('en-US')%>
|
||||
<% } %>
|
||||
<p>You can download these files by logging in to <a href="<%= adminUrl %>"><%= adminUrl %></a></p>
|
||||
<p>You can download these files by logging in to <a href="<%=adminUrl%>"><%=adminUrl%></a></p>
|
||||
<p>Cheers,<br />Recv</p>
|
@ -1 +1 @@
|
||||
📄 File upload notification
|
||||
📄 <%=upload.files.count%> file<%=upload.files.count != 1 ? 's' : ''%> uploaded
|
4
index.js
4
index.js
@ -1,13 +1,13 @@
|
||||
'use strict'
|
||||
const fs = require('fs');
|
||||
const merge = require('deepmerge');
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
let configDefaults = require('./config.defaults');
|
||||
if (fs.existsSync('./config.js'))
|
||||
{
|
||||
let configChanges = require('./config');
|
||||
global.config = merge(configDefaults, configChanges);
|
||||
global.config = _.merge(configDefaults, configChanges);
|
||||
}
|
||||
else
|
||||
global.config = configDefaults;
|
||||
|
387
lib/api/admin.js
387
lib/api/admin.js
@ -1,387 +0,0 @@
|
||||
const express = require('express');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const path = require('path');
|
||||
const resolvePath = require('resolve-path');
|
||||
const AuthTokens = require('../authtokens');
|
||||
const ExpirationUnits = require('../expirationunits');
|
||||
const disk = require('diskusage');
|
||||
const fs = require('mz/fs');
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, repository, onVerified)
|
||||
{
|
||||
var token;
|
||||
|
||||
if (req.headers.authorization)
|
||||
{
|
||||
if (req.headers.authorization.split(' ')[0] !== 'Bearer')
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
else if (req.cookies && req.cookies.adminToken)
|
||||
{
|
||||
token = req.cookies.adminToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
jwt.verify(token, config.jwtSecret, async (err, decoded) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoded.userId)
|
||||
{
|
||||
var user = await repository.users.get(decoded.userId);
|
||||
if (user === null || !user.active)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
else
|
||||
await onVerified(user);
|
||||
}
|
||||
else
|
||||
res.sendStatus(400);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
var router = express.Router();
|
||||
|
||||
|
||||
router.get('/whoami', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
res.send({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
auth: user.auth
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/diskspace', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
disk.check(config.fileUpload.path, (err, info) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(info);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
/*
|
||||
Codes
|
||||
*/
|
||||
router.get('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var codes = await repository.codes.list(user.hasAuth(AuthTokens.ViewAllCodes) ? null : user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
|
||||
codes.forEach((item) =>
|
||||
{
|
||||
item.username = usernames[item.userId];
|
||||
});
|
||||
|
||||
res.send(codes);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code === null || (code.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(code.userId);
|
||||
if (user !== null)
|
||||
code.username = user.name;
|
||||
|
||||
res.send(code);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var postedCode = req.body;
|
||||
|
||||
|
||||
if (config.code.maxExpiration !== null)
|
||||
{
|
||||
let now = new Date();
|
||||
|
||||
if (ExpirationUnits.apply(postedCode.expiration) > ExpirationUnits.apply(config.code.maxExpiration))
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (postedCode.id)
|
||||
{
|
||||
var code = await repository.codes.get(postedCode.id);
|
||||
if (code === null || (code.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.codes.update({
|
||||
id: postedCode.id,
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
var codeId = await repository.codes.insert({
|
||||
userId: user.id,
|
||||
created: postedCode.created || new Date(),
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
}
|
||||
|
||||
res.send(codeId);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code == null || (code.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.codes.delete(code.id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/expiration', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
res.send({
|
||||
max: config.code.maxExpiration,
|
||||
default: config.code.defaultExpiration
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
/*
|
||||
Uploads
|
||||
*/
|
||||
router.get('/uploads', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var files = await repository.uploads.list(user.hasAuth(AuthTokens.ViewAllUploads) ? null : user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
var codedescriptions = await repository.codes.getDescriptions();
|
||||
|
||||
files.forEach((item) =>
|
||||
{
|
||||
item.username = item.userId !== null ? usernames[item.userId] : null;
|
||||
item.codedescription = item.code !== null ? codedescriptions[item.code] : null;
|
||||
});
|
||||
|
||||
res.send(files);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/uploads/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var upload = await repository.uploads.get(req.params.id);
|
||||
if (upload == null || (upload.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllUploads)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.uploads.delete(upload.id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/download/:fileid/:displayname', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
// TODO should we check if the user has access to the file?
|
||||
// for now not that important, if you know the file's UID and are logged in
|
||||
|
||||
var fullpath = resolvePath(config.fileUpload.path, req.params.fileid);
|
||||
res.download(fullpath, req.params.displayname);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
/*
|
||||
Users
|
||||
*/
|
||||
router.get('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
if (!user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var users = await repository.users.list();
|
||||
res.send(users);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
if (req.params.id !== user.id && !user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(req.params.id);
|
||||
if (user === null)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(user);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var postedUser = req.body;
|
||||
|
||||
if (postedUser.id)
|
||||
{
|
||||
if (postedUser.id !== user.id && !user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.users.update({
|
||||
id: postedUser.id,
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = await repository.users.insert({
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active,
|
||||
createdByUserId: postedUser.createdByUserId
|
||||
});
|
||||
}
|
||||
|
||||
res.send(userId);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
if (!user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.users.delete(req.params.id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}));
|
||||
|
||||
return router;
|
||||
}
|
171
lib/api/admin/codes.js
Normal file
171
lib/api/admin/codes.js
Normal file
@ -0,0 +1,171 @@
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const AuthTokens = require('../../authtokens');
|
||||
const ExpirationUnits = require('../../expirationunits');
|
||||
const NotificationType = require('../../repository/notification').NotificationType;
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var codes = await repository.codes.list(req.user.hasAuth(AuthTokens.ViewAllCodes) ? null : req.user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
|
||||
codes.forEach((item) =>
|
||||
{
|
||||
item.username = usernames[item.userId];
|
||||
});
|
||||
|
||||
res.send(codes);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code === null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(code.userId);
|
||||
if (user !== null)
|
||||
code.username = user.name;
|
||||
|
||||
res.send(code);
|
||||
}));
|
||||
|
||||
|
||||
router.post('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var postedCode = req.body;
|
||||
|
||||
|
||||
if (config.code.maxExpiration !== null)
|
||||
{
|
||||
let now = new Date();
|
||||
|
||||
if (ExpirationUnits.apply(postedCode.expiration) > ExpirationUnits.apply(config.code.maxExpiration))
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (postedCode.id)
|
||||
{
|
||||
var code = await repository.codes.get(postedCode.id);
|
||||
if (code === null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.codes.update({
|
||||
id: postedCode.id,
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
var codeId = await repository.codes.insert({
|
||||
userId: req.user.id,
|
||||
created: postedCode.created || new Date(),
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
}
|
||||
|
||||
res.send(codeId);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code == null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.codes.delete(code.id);
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/expiration', asyncHandler(async (req, res) =>
|
||||
{
|
||||
res.send({
|
||||
max: config.code.maxExpiration,
|
||||
default: config.code.defaultExpiration
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/assign/code', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var postedCode = req.body;
|
||||
|
||||
var code = await repository.codes.get(postedCode.id);
|
||||
if (code === null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (code.userId !== postedCode.userId)
|
||||
{
|
||||
var target = await repository.users.get(postedCode.userId);
|
||||
if (target === null)
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.codes.move(postedCode.id, postedCode.userId);
|
||||
await repository.uploads.move(postedCode.id, postedCode.userId);
|
||||
|
||||
await repository.notifications.insert({
|
||||
userId: postedCode.userId,
|
||||
codeId: postedCode.id,
|
||||
type: NotificationType.CodeMoved,
|
||||
metadata: {
|
||||
prevUserId: code.userId,
|
||||
assignUserId: req.user.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/assign/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var users = await repository.users.list();
|
||||
if (users === null)
|
||||
{
|
||||
res.send([]);
|
||||
return;
|
||||
}
|
||||
|
||||
let assignableUsers = _.map(_.filter(users,
|
||||
(user) => { return user.active }),
|
||||
(user) => { return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name
|
||||
}} );
|
||||
|
||||
res.send(assignableUsers);
|
||||
}));
|
||||
}
|
88
lib/api/admin/index.js
Normal file
88
lib/api/admin/index.js
Normal file
@ -0,0 +1,88 @@
|
||||
const express = require('express');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, repository, onVerified)
|
||||
{
|
||||
var token;
|
||||
|
||||
if (req.headers.authorization)
|
||||
{
|
||||
if (req.headers.authorization.split(' ')[0] !== 'Bearer')
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
else if (req.cookies && req.cookies.adminToken)
|
||||
{
|
||||
token = req.cookies.adminToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
jwt.verify(token, config.jwtSecret, async (err, decoded) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoded.userId)
|
||||
{
|
||||
var user = await repository.users.get(decoded.userId);
|
||||
if (user === null || !user.active)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
else
|
||||
await onVerified(user);
|
||||
}
|
||||
else
|
||||
res.sendStatus(400);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
var router = express.Router();
|
||||
router.use(async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await checkAuthorization(req, res, repository, (user) =>
|
||||
{
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
require('./status')(repository, router);
|
||||
require('./codes')(repository, router);
|
||||
require('./uploads')(repository, router);
|
||||
require('./users')(repository, router);
|
||||
|
||||
return router;
|
||||
}
|
30
lib/api/admin/status.js
Normal file
30
lib/api/admin/status.js
Normal file
@ -0,0 +1,30 @@
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const disk = require('diskusage');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/whoami', (req, res) =>
|
||||
{
|
||||
res.send({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
auth: req.user.auth
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
router.get('/diskspace', (req, res) =>
|
||||
{
|
||||
disk.check(config.fileUpload.path, (err, info) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(info);
|
||||
});
|
||||
});
|
||||
}
|
74
lib/api/admin/uploads.js
Normal file
74
lib/api/admin/uploads.js
Normal file
@ -0,0 +1,74 @@
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const AuthTokens = require('../../authtokens');
|
||||
const resolvePath = require('resolve-path');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/uploads', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var files = await repository.uploads.list(req.user.hasAuth(AuthTokens.ViewAllUploads) ? null : req.user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
var codedescriptions = await repository.codes.getDescriptions();
|
||||
|
||||
files.forEach((item) =>
|
||||
{
|
||||
item.username = item.userId !== null ? usernames[item.userId] : null;
|
||||
item.codedescription = item.codeId !== null ? codedescriptions[item.codeId] : null;
|
||||
});
|
||||
|
||||
res.send(files);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/uploads/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var upload = await repository.uploads.get(req.params.id);
|
||||
if (upload == null || (upload.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllUploads)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.uploads.delete(upload.id);
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/codeuploads/:code', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var uploads = await repository.uploads.listForCode(req.params.code);
|
||||
if (uploads === null)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.user.hasAuth(AuthTokens.ViewAllUploads))
|
||||
{
|
||||
for (let i = 0; i < uploads.length; i++)
|
||||
{
|
||||
if (uploads[i].userId !== req.user.id)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < uploads.length; i++)
|
||||
await repository.uploads.delete(uploads[i].id);
|
||||
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/download/:fileid/:displayname', asyncHandler(async (req, res) =>
|
||||
{
|
||||
// TODO should we check if the user has access to the file?
|
||||
// for now not that important, if you know the file's UID and are logged in
|
||||
|
||||
var fullpath = resolvePath(config.fileUpload.path, req.params.fileid);
|
||||
res.download(fullpath, req.params.displayname);
|
||||
}));
|
||||
}
|
97
lib/api/admin/users.js
Normal file
97
lib/api/admin/users.js
Normal file
@ -0,0 +1,97 @@
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const AuthTokens = require('../../authtokens');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var users = await repository.users.list();
|
||||
res.send(users);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (req.params.id !== req.user.id && !req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(req.params.id);
|
||||
if (user === null)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(user);
|
||||
}));
|
||||
|
||||
|
||||
router.post('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var postedUser = req.body;
|
||||
|
||||
if (postedUser.id)
|
||||
{
|
||||
if (postedUser.id !== req.user.id && !req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.users.update({
|
||||
id: postedUser.id,
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = await repository.users.insert({
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active,
|
||||
createdByUserId: postedUser.createdByUserId
|
||||
});
|
||||
}
|
||||
|
||||
res.send(userId);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.users.delete(req.params.id);
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
}
|
@ -19,7 +19,7 @@ module.exports = (repository) =>
|
||||
if (code !== null)
|
||||
{
|
||||
jwt.sign({
|
||||
code: req.body.code,
|
||||
codeId: req.body.code,
|
||||
codeUserId: code.userId,
|
||||
codeExpirationTime: code.expirationDate !== null ? code.expirationDate.getTime() : null
|
||||
}, config.jwtSecret, (err, token) =>
|
||||
|
@ -4,6 +4,7 @@ const jwt = require('jsonwebtoken');
|
||||
const resolvePath = require('resolve-path');
|
||||
const fs = require('fs');
|
||||
const async = require('async');
|
||||
const NotificationType = require('../repository/notification').NotificationType;
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, onVerified)
|
||||
@ -25,7 +26,7 @@ async function checkAuthorization(req, res, onVerified)
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoded.code)
|
||||
if (decoded.codeId)
|
||||
await onVerified(decoded);
|
||||
else
|
||||
res.sendStatus(400);
|
||||
@ -106,16 +107,17 @@ module.exports = (repository, tusServer) =>
|
||||
return;
|
||||
}
|
||||
|
||||
var uploadId = await repository.uploads.insert(
|
||||
decoded.codeUserId,
|
||||
decoded.code,
|
||||
req.body.files,
|
||||
decoded.codeExpirationTime !== null ? new Date(decoded.codeExpirationTime) : null
|
||||
);
|
||||
var uploadId = await repository.uploads.insert({
|
||||
userId: decoded.codeUserId,
|
||||
codeId: decoded.codeId,
|
||||
files: req.body.files,
|
||||
expirationDate: decoded.codeExpirationTime !== null ? new Date(decoded.codeExpirationTime) : null
|
||||
});
|
||||
|
||||
await repository.notifications.insert({
|
||||
userId: decoded.codeUserId,
|
||||
uploadId: uploadId
|
||||
uploadId: uploadId,
|
||||
type: NotificationType.UploadComplete
|
||||
});
|
||||
|
||||
res.send({ id: uploadId });
|
||||
|
@ -20,6 +20,10 @@ class Code
|
||||
self.description = values.description || null;
|
||||
self.message = values.message || null;
|
||||
self.messageHTML = values.messageHTML || null;
|
||||
self.history = values.history || [{
|
||||
userId: self.userId,
|
||||
date: self.created
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,15 +59,23 @@ class CodeRepository
|
||||
if ((await self.get(codeId)) !== null)
|
||||
throw new Error('Code ' + codeId + ' already exists');
|
||||
|
||||
let now = new Date();
|
||||
|
||||
self.store.insert({
|
||||
_id: codeId,
|
||||
userId: code.userId,
|
||||
created: code.created || new Date(),
|
||||
created: code.created || now,
|
||||
expiration: code.expiration,
|
||||
expirationDate: ExpirationUnits.apply(code.expiration, code.created),
|
||||
description: code.description,
|
||||
message: code.message,
|
||||
messageHTML: self.getMessageHTML(code.message)
|
||||
messageHTML: self.getMessageHTML(code.message),
|
||||
history: [
|
||||
{
|
||||
userId: code.userId,
|
||||
date: code.created || now
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return codeId;
|
||||
@ -107,6 +119,42 @@ class CodeRepository
|
||||
}
|
||||
|
||||
|
||||
move(codeId, userId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.update({ _id: codeId }, {
|
||||
$set: {
|
||||
userId: userId
|
||||
},
|
||||
$push: {
|
||||
history: {
|
||||
userId: userId,
|
||||
date: new Date()
|
||||
}
|
||||
}
|
||||
},
|
||||
(err, numAffected) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (numAffected == 0)
|
||||
{
|
||||
reject();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
list(userId)
|
||||
{
|
||||
var self = this;
|
||||
@ -180,13 +228,13 @@ class CodeRepository
|
||||
}
|
||||
|
||||
|
||||
delete(code)
|
||||
delete(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.remove({ _id: code }, (err, numRemoved) =>
|
||||
self.store.remove({ _id: codeId }, (err, numRemoved) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
|
@ -1,6 +1,12 @@
|
||||
const map = require('lodash/map');
|
||||
|
||||
|
||||
const NotificationType = {
|
||||
UploadComplete: 'uploadComplete',
|
||||
CodeMoved: 'codeMoved'
|
||||
}
|
||||
|
||||
|
||||
class Notification
|
||||
{
|
||||
constructor(values)
|
||||
@ -8,9 +14,12 @@ class Notification
|
||||
var self = this;
|
||||
|
||||
self.id = values.id || values._id || null;
|
||||
self.userId = values.userId || null;
|
||||
self.uploadId = values.uploadId || null;
|
||||
self.codeId = values.codeId || null;
|
||||
self.userId = values.userId || null;
|
||||
self.type = values.type || NotificationType.UploadComplete;
|
||||
self.attempt = values.attempt || 0;
|
||||
self.metadata = values.metadata || null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,8 +61,11 @@ class NotificationRepository
|
||||
{
|
||||
self.store.insert({
|
||||
userId: notification.userId,
|
||||
codeId: notification.codeId,
|
||||
uploadId: notification.uploadId,
|
||||
attempt: notification.attempt
|
||||
attempt: notification.attempt,
|
||||
type: notification.type,
|
||||
metadata: notification.metadata
|
||||
}, (err, dbNotification) =>
|
||||
{
|
||||
if (err)
|
||||
@ -118,6 +130,7 @@ class NotificationRepository
|
||||
|
||||
|
||||
module.exports = {
|
||||
NotificationType,
|
||||
Notification,
|
||||
NotificationRepository
|
||||
}
|
@ -13,7 +13,7 @@ class Upload
|
||||
|
||||
self.id = values.id || values._id || null;
|
||||
self.userId = values.userId || null;
|
||||
self.code = values.code || null;
|
||||
self.codeId = values.codeId || null;
|
||||
self.created = values.created || new Date();
|
||||
self.expirationDate = values.expirationDate || null;
|
||||
self.files = values.files || [];
|
||||
@ -54,25 +54,25 @@ class UploadRepository
|
||||
}
|
||||
|
||||
|
||||
insert(userId, code, files, expirationDate)
|
||||
insert(upload)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
var upload = {
|
||||
created: new Date(),
|
||||
userId: userId,
|
||||
code: code,
|
||||
expirationDate: expirationDate,
|
||||
files: map(filter(files,
|
||||
let insertUpload = {
|
||||
created: upload.created || new Date(),
|
||||
userId: upload.userId,
|
||||
codeId: upload.codeId,
|
||||
expirationDate: upload.expirationDate,
|
||||
files: map(filter(upload.files,
|
||||
(file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')),
|
||||
(file) => { return { id: file.id, name: file.name, size: file.size } })
|
||||
};
|
||||
|
||||
if (upload.files.length)
|
||||
if (insertUpload.files.length)
|
||||
{
|
||||
self.store.insert(upload, (err, dbUpload) =>
|
||||
self.store.insert(insertUpload, (err, dbUpload) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
@ -114,6 +114,29 @@ class UploadRepository
|
||||
}
|
||||
|
||||
|
||||
listForCode(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.find({ codeId: codeId }, (err, docs) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(docs.map((dbUpload) =>
|
||||
{
|
||||
return new Upload(dbUpload);
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
get(uploadId)
|
||||
{
|
||||
var self = this;
|
||||
@ -134,6 +157,36 @@ class UploadRepository
|
||||
}
|
||||
|
||||
|
||||
move(codeId, userId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.update({ codeId: codeId }, {
|
||||
$set: {
|
||||
userId: userId
|
||||
}
|
||||
},
|
||||
(err, numAffected) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (numAffected == 0)
|
||||
{
|
||||
reject();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
delete(uploadId)
|
||||
{
|
||||
var self = this;
|
||||
|
@ -5,6 +5,32 @@ const fs = require('mz/fs');
|
||||
const path = require('path');
|
||||
const getPaths = require('get-paths');
|
||||
const AbstractIntervalWorker = require('./abstractintervalworker');
|
||||
const NotificationType = require('../repository/notification').NotificationType;
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
function humanFileSize(bytes, si)
|
||||
{
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if(Math.abs(bytes) < thresh)
|
||||
{
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
var units = si
|
||||
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||
var u = -1;
|
||||
|
||||
do
|
||||
{
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
}
|
||||
while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
|
||||
|
||||
class NotificationWorker extends AbstractIntervalWorker
|
||||
@ -24,7 +50,7 @@ class NotificationWorker extends AbstractIntervalWorker
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
async.eachOfSeries(notifications,
|
||||
async (item) => { await self.sendNotification(item) },
|
||||
async (item) => { await self.handleNotification(item) },
|
||||
(err) =>
|
||||
{
|
||||
if (err)
|
||||
@ -36,17 +62,104 @@ class NotificationWorker extends AbstractIntervalWorker
|
||||
}
|
||||
|
||||
|
||||
async sendNotification(notification)
|
||||
async handleNotification(notification)
|
||||
{
|
||||
let self = this;
|
||||
|
||||
let user = await self.repository.users.get(notification.userId);
|
||||
if (user === null || !user.email)
|
||||
return;
|
||||
|
||||
let upload = await self.repository.uploads.get(notification.uploadId);
|
||||
if (upload === null)
|
||||
switch (notification.type)
|
||||
{
|
||||
case NotificationType.UploadComplete:
|
||||
await self.sendUploadNotification(notification, user);
|
||||
break;
|
||||
|
||||
case NotificationType.CodeMoved:
|
||||
await self.sendCodeMovedNotification(notification, user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getDefaultLocals(user)
|
||||
{
|
||||
return {
|
||||
// Data
|
||||
user: user,
|
||||
|
||||
// Configuration
|
||||
adminUrl: config.notifications.adminUrl,
|
||||
|
||||
// Helper functions
|
||||
humanFileSize: humanFileSize
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async sendUploadNotification(notification, user)
|
||||
{
|
||||
let self = this;
|
||||
let locals = self.getDefaultLocals(user);
|
||||
|
||||
locals.upload = await self.repository.uploads.get(notification.uploadId);
|
||||
if (locals.upload === null)
|
||||
return;
|
||||
|
||||
await self.sendNotification(notification, user, locals, 'uploadnotification');
|
||||
}
|
||||
|
||||
|
||||
async sendCodeMovedNotification(notification, user)
|
||||
{
|
||||
let self = this;
|
||||
let locals = self.getDefaultLocals(user);
|
||||
|
||||
locals.uploads = await self.repository.uploads.listForCode(notification.codeId);
|
||||
if (locals.uploads === null || locals.uploads.length == 0)
|
||||
return;
|
||||
|
||||
locals.fileCount = 0;
|
||||
_.forEach(locals.uploads, (upload) =>
|
||||
{
|
||||
locals.fileCount += upload.files.length;
|
||||
});
|
||||
|
||||
locals.prevUser = {
|
||||
userId: null,
|
||||
name: ''
|
||||
};
|
||||
|
||||
if (notification.metadata !== null)
|
||||
{
|
||||
let resolveUser = async (userId) =>
|
||||
{
|
||||
if (!userId)
|
||||
return null;
|
||||
|
||||
let user = await self.repository.users.get(userId);
|
||||
if (user !== null)
|
||||
{
|
||||
return {
|
||||
userId: userId,
|
||||
name: user.name
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
locals.prevUser = await resolveUser(notification.metadata.prevUserId);
|
||||
locals.assignUser = await resolveUser(notification.metadata.assignUserId);
|
||||
}
|
||||
|
||||
await self.sendNotification(notification, user, locals, 'movednotification');
|
||||
}
|
||||
|
||||
|
||||
async sendNotification(notification, user, locals, template)
|
||||
{
|
||||
let self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
const email = new Email({
|
||||
@ -99,24 +212,21 @@ class NotificationWorker extends AbstractIntervalWorker
|
||||
|
||||
email
|
||||
.send({
|
||||
template: 'uploadnotification',
|
||||
template: template,
|
||||
message: {
|
||||
to: user.email
|
||||
},
|
||||
locals: {
|
||||
user: user,
|
||||
upload: upload,
|
||||
adminUrl: config.notifications.adminUrl
|
||||
}
|
||||
locals: locals
|
||||
})
|
||||
.then(async () =>
|
||||
{
|
||||
await self.repository.notifications.delete(notification.id);
|
||||
console.log('Notification sent to: ' + user.email + ' for upload ID: ' + upload.id);
|
||||
console.log('Notification sent to: ' + user.email);
|
||||
resolve();
|
||||
})
|
||||
.catch(async (error) =>
|
||||
{
|
||||
console.log(error);
|
||||
notification.attempt++;
|
||||
if (notification.attempt > config.notifications.maxAttempts)
|
||||
await self.repository.notifications.delete(notification.id);
|
||||
|
5
package-lock.json
generated
5
package-lock.json
generated