Added expiration settings for codes

Added missing Dutch translations
This commit is contained in:
Mark van Renswoude 2018-05-02 20:07:09 +02:00
parent 2f46dbff2c
commit bb91384cab
13 changed files with 439 additions and 237 deletions

37
README.md Normal file
View File

@ -0,0 +1,37 @@
### Installation
Copy ```config.example.js``` to ```config.js``` and review the configuration options. If this is an update, compare your ```config.js``` with ```config.example.js``` to see if anything has been added.
To build the production version:
```bash
npm run build
```
#### Service on Linux (systemd)
/etc/systemd/system/recv.service:
```ini
[Unit]
Description=Recv
[Service]
ExecStart=/usr/bin/node /srv/recv/index.js
WorkingDirectory=/srv/recv
Restart=always
RestartSec=10 # Restart service after 10 seconds if node service crashes
StandardOutput=syslog # Output to syslog
StandardError=syslog # Output to syslog
SyslogIdentifier=recv
User=recv
[Install]
WantedBy=multi-user.target
```
```bash
systemctl enable recv.service
systemctl start recv.service
```

View File

@ -1,4 +1,6 @@
const path = require('path');
const ExpirationUnits = require('./lib/expirationunits');
module.exports = {
port: 3000,
@ -21,7 +23,15 @@ module.exports = {
// Use https://alex7kom.github.io/nano-nanoid-cc/ to check for the chance of collisions.
// If a collision occurs, a new code will be generated and tested again for up to 100 times.
alphabet: '1234567890abcdef',
length: 8
length: 8,
maxExpiration: null
/*
maxExpiration: {
units: ExpirationUnits.Days,
value: 7
}
*/
},
notifications: {

View File

@ -5,6 +5,7 @@ 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');
@ -150,6 +151,19 @@ module.exports = (repository) =>
{
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);
@ -172,6 +186,7 @@ module.exports = (repository) =>
{
var codeId = await repository.codes.insert({
userId: user.id,
created: postedCode.created || new Date(),
expiration: postedCode.expiration,
description: postedCode.description,
message: postedCode.message
@ -199,6 +214,17 @@ module.exports = (repository) =>
});
}));
router.get('/maxExpiration', asyncHandler(async (req, res) =>
{
await checkAuthorization(req, res, repository, async (user) =>
{
res.send(config.code.maxExpiration !== null
? config.code.maxExpiration
: { units: '', value: 1 });
});
}));
/*
Uploads
*/

View File

@ -16,12 +16,13 @@ module.exports = (repository) =>
return;
}
var userId = await repository.codes.getUserId(req.body.code);
if (userId !== null)
var code = await repository.codes.get(req.body.code);
if (code !== null)
{
jwt.sign({
code: req.body.code,
codeUserId: userId
codeUserId: code.userId,
codeExpirationDate: code.expirationDate
}, config.jwtSecret, (err, token) =>
{
if (err)

View File

@ -80,8 +80,6 @@ module.exports = (repository, tusServer) =>
await checkAuthorization(req, res, async (decoded) =>
{
var expiration = null; // TODO set expiration properties
async.each(req.body.files, (item, callback) =>
{
if (!item.id)
@ -105,7 +103,7 @@ module.exports = (repository, tusServer) =>
return;
}
var uploadId = await repository.uploads.insert(decoded.codeUserId, decoded.code, req.body.files, expiration);
var uploadId = await repository.uploads.insert(decoded.codeUserId, decoded.code, req.body.files, decoded.codeExpirationDate);
await repository.notifications.insert({
userId: decoded.codeUserId,
uploadId: uploadId

23
lib/expirationunits.js Normal file
View File

@ -0,0 +1,23 @@
const moment = require('moment');
const ExpirationUnits = {
Hours: 'h',
Days: 'd',
Weeks: 'w',
Months: 'M',
Years: 'y',
apply: (expiration, date) =>
{
if (!expiration) return null;
let reference = moment(date);
if (!reference.isValid())
reference = moment();
return reference.add(expiration.value, expiration.units).toDate();
}
}
module.exports = ExpirationUnits;

View File

@ -3,6 +3,7 @@ const retry = require('async-retry');
const generate = require('nanoid/generate');
const markdown = require('markdown').markdown;
const config = require('../../config');
const ExpirationUnits = require('../expirationunits');
class Code
@ -15,6 +16,7 @@ class Code
self.userId = values.userId || null;
self.created = values.created || new Date();
self.expiration = values.expiration || null;
self.expirationDate = values.expirationDate || null;
self.description = values.description || null;
self.message = values.message || null;
self.messageHTML = values.messageHTML || null;
@ -42,26 +44,6 @@ class CodeRepository
}
getUserId(code)
{
var self = this;
return new Promise((resolve, reject) =>
{
self.store.findOne({ _id: code }, (err, doc) =>
{
if (err)
{
reject(err);
return;
}
resolve(doc !== null ? doc.userId : null);
});
});
}
async insert(code)
{
var self = this;
@ -70,7 +52,7 @@ class CodeRepository
{
var codeId = generate(config.code.alphabet, config.code.length);
if ((await self.getUserId(codeId)) !== null)
if ((await self.get(codeId)) !== null)
throw new Error('Code ' + codeId + ' already exists');
self.store.insert({
@ -78,6 +60,7 @@ class CodeRepository
userId: code.userId,
created: code.created || new Date(),
expiration: code.expiration,
expirationDate: ExpirationUnits.apply(code.expiration, code.created),
description: code.description,
message: code.message,
messageHTML: self.getMessageHTML(code.message)
@ -100,6 +83,7 @@ class CodeRepository
{
self.store.update({ _id: code.id }, { $set: {
expiration: code.expiration,
expirationDate: ExpirationUnits.apply(code.expiration, code.created),
description: code.description,
message: code.message,
messageHTML: self.getMessageHTML(code.message)

View File

@ -12,7 +12,7 @@ class Upload
self.userId = values.userId || null;
self.code = values.code || null;
self.created = values.created || new Date();
self.expiration = values.expiration || null;
self.expirationDate = values.expirationDate || null;
self.files = values.files || [];
}
}
@ -51,7 +51,7 @@ class UploadRepository
}
insert(userId, code, files, expiration)
insert(userId, code, files, expirationDate)
{
var self = this;
@ -61,7 +61,7 @@ class UploadRepository
created: new Date(),
userId: userId,
code: code,
expiration: expiration,
expirationDate: expirationDate,
files: map(filter(files,
(file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')),
(file) => { return { id: file.id, name: file.name, size: file.size } })

392
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -102,6 +102,12 @@ a
color: #228dd4;
}
.pure-form select
{
padding-top: 0;
padding-bottom: 0;
}
/*
pure-g uses hardcoded sans-serif to get accurate alignment, this class
is added as a workaround for text in rows

View File

@ -74,7 +74,25 @@ export default {
descriptionHint: 'The description will be visible only in the admin panel',
expiration: 'Expiration',
message: 'Message',
messageHint: 'The message will be displayed to the user on the upload page after the code is entered. Markdown is supported.'
messageHint: 'The message will be displayed to the user on the upload page after the code is entered. Markdown is supported.',
expirationUnits: {
never: 'Never',
h: 'Hours:',
d: 'Days:',
w: 'Weeks:',
M: 'Months:',
y: 'Years:'
},
expirationHint: {
format: 'The maximum allowed expiration is %{valueUnits}',
h: '0 hours | 1 hour | {count} hours',
d: '0 days | 1 day | {count} days',
w: '0 weeks | 1 week | {count} weeks',
M: '0 months | 1 month | {count} months',
y: '0 years | 1 year | {count} years'
}
}
},

View File

@ -74,7 +74,51 @@ export default {
descriptionHint: 'De omschrijving is alleen zichtbaar in beheer',
expiration: 'Verloopt',
message: 'Bericht',
messageHint: 'Het bericht wordt getoond aan de gebruiker op de upload pagina na het invoeren van de code. Markdown kan worden gebruikt.'
messageHint: 'Het bericht wordt getoond aan de gebruiker op de upload pagina na het invoeren van de code. Markdown kan worden gebruikt.',
expirationUnits: {
never: 'Nooit',
h: 'Uren:',
d: 'Dagen:',
w: 'Weken:',
M: 'Maanden:',
y: 'Jaren:'
},
expirationHint: {
format: 'De maximaal toegestane verlooptermijn is %{valueUnits}',
h: '0 uren | 1 uur | {value} uren',
d: '0 dagen | 1 dag | {value} dagen',
w: '0 weken | 1 week | {value} weken',
M: '0 maanden | 1 maand | {value} maanden',
y: '0 jaren | 1 jaar | {value} jaren'
}
}
},
users: {
add: 'Gebruiker toevoegen',
list: {
username: '(Gebruikers)naam',
inactive: '(inactief)',
email: 'Email',
actions: 'Acties'
},
detail: {
username: 'Gebruikersnaam',
name: 'Naam',
email: 'Email',
password: 'Wachtwoord',
passwordHint: 'Vul een nieuw wachtwoord in om deze te wijzigen. Laat leeg om het huidige wachtwoord te gebruiken.',
auth: {
title: 'Machtigingen',
viewAllCodes: 'Codes beheren die door andere gebruikers zijn aangemaakt',
viewAllUploads: 'Uploads beheren die door andere gebruikers zijn aangemaakt',
manageUsers: 'Gebruikers beheren'
},
active: 'Actief'
}
}
}

View File

@ -30,12 +30,23 @@
{{ $t('admin.codes.detail.descriptionHint') }}
</div>
<!--
<div class="pure-control-group">
<label for="expiration">{{ $t('admin.codes.detail.expiration') }}</label>
<input id="expiration" type="text" :value="code.expiration" class="pure-input-2-3">
<select v-if="!code.id" id="expirationUnits" v-model="code.expiration.units" class="pure-input-1-3">
<option value="" v-if="allowExpiration(null)">{{ $t('admin.codes.detail.expirationUnits.never') }}</option>
<option value="h" v-if="allowExpiration('h')">{{ $t('admin.codes.detail.expirationUnits.h') }}</option>
<option value="d" v-if="allowExpiration('d')">{{ $t('admin.codes.detail.expirationUnits.d') }}</option>
<option value="w" v-if="allowExpiration('w')">{{ $t('admin.codes.detail.expirationUnits.w') }}</option>
<option value="M" v-if="allowExpiration('M')">{{ $t('admin.codes.detail.expirationUnits.M') }}</option>
<option value="y" v-if="allowExpiration('y')">{{ $t('admin.codes.detail.expirationUnits.y') }}</option>
</select>
<input v-if="!code.id" id="expiration" type="number" v-model="code.expiration.value" :disabled="!code.expiration.units" min="1" class="pure-input-1-3">
<input v-if="code.id" id="expiration" type="text" readonly :value="code.expirationDate | formatDateTime" class="pure-input-2-3">
</div>
<div class="pure-form-description" v-if="!code.id && maxExpiration !== null">
{{ $t('admin.codes.detail.expirationHint.format', { valueUnits: getExpirationValueUnits() }) }}
</div>
-->
<div class="pure-control-group">
<label for="message">{{ $t('admin.codes.detail.message') }}</label>
@ -67,6 +78,7 @@ export default {
{
return {
code: null,
maxExpiration: null,
saving: false
};
},
@ -109,10 +121,24 @@ export default {
}
else
{
self.code = {
expiration: null,
message: null
};
axios.get('/admin/maxExpiration', {
headers: {
Authorization: 'Bearer ' + shared.adminToken
}
})
.then((response) =>
{
self.maxExpiration = response.data.units ? {
units: response.data.units,
value: response.data.value
} : null;
self.code = {
expiration: response.data,
message: null
};
})
.catch((error) => { shared.$emit('apiError', error, this.$router) });
}
},
@ -133,11 +159,13 @@ export default {
{
var self = this;
// TODO check against maxExpiration, otherwise the server will simply throw a 400 Bad Request
self.saving = true;
axios.post('/admin/codes', {
id: self.code.id,
expiration: self.code.expiration,
expiration: self.code.expiration.units ? self.code.expiration : null,
description: self.code.description,
message: self.code.message
}, {
@ -157,6 +185,33 @@ export default {
{
self.saving = false;
});
},
allowExpiration(units)
{
var self = this;
if (units === null)
return self.maxExpiration === null;
if (self.maxExpiration === null)
return false;
let validUnits = ['h', 'd', 'w', 'M', 'y'];
return validUnits.indexOf(units) <= validUnits.indexOf(self.maxExpiration.units);
},
getExpirationValueUnits()
{
var self = this;
if (self.maxExpiration === null)
return '';
return self.$tc('admin.codes.detail.expirationHint.' + self.maxExpiration.units,
self.maxExpiration.value,
{ count: self.maxExpiration.value });
}
}
}