Added expiration settings for codes
Added missing Dutch translations
This commit is contained in:
parent
2f46dbff2c
commit
bb91384cab
|
@ -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
|
||||
```
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
|
@ -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)
|
||||
|
|
|
@ -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 } })
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue