Further admin preparations
Improved exception handling in async functions
This commit is contained in:
parent
a07c9a3ef7
commit
8b9ed0e666
|
@ -3,4 +3,5 @@ data
|
|||
custom/*.js
|
||||
public/dist
|
||||
config.js
|
||||
*.sublime-workspace
|
||||
*.sublime-workspace
|
||||
/npm-debug.log
|
87
index.js
87
index.js
|
@ -16,69 +16,62 @@ const webpackDevMiddleware = require('webpack-dev-middleware');
|
|||
const webpackHotMiddleware = require('webpack-hot-middleware');
|
||||
const webpackConfig = require('./webpack.config.js');
|
||||
|
||||
|
||||
(async function()
|
||||
(async () =>
|
||||
{
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
try
|
||||
{
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const repository = new Repository(config.database);
|
||||
await repository.load()
|
||||
.catch((err) =>
|
||||
{
|
||||
console.log(err);
|
||||
const repository = new Repository(config.database);
|
||||
await repository.load();
|
||||
|
||||
const tusServer = new tus.Server();
|
||||
tusServer.datastore = new tus.FileStore({
|
||||
path: config.fileUpload.url,
|
||||
directory: config.fileUpload.path
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
const tusServer = new tus.Server();
|
||||
tusServer.datastore = new tus.FileStore({
|
||||
path: config.fileUpload.url,
|
||||
directory: config.fileUpload.path
|
||||
});
|
||||
app.disable('x-powered-by');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
|
||||
|
||||
const registerAPI = (name) => { require('./lib/api/' + name)(app, repository) }
|
||||
const loadAPI = (route, name) => { app.use(route, require('./lib/api/' + name)(repository)) }
|
||||
|
||||
fs.readdir('./lib/api', (err, files) =>
|
||||
{
|
||||
if (err)
|
||||
loadAPI('/', 'upload');
|
||||
loadAPI('/token', 'token');
|
||||
loadAPI('/admin', 'admin');
|
||||
|
||||
|
||||
// Frontend
|
||||
if (isDevelopment)
|
||||
{
|
||||
console.log(err);
|
||||
return;
|
||||
const compiler = webpack(webpackConfig);
|
||||
|
||||
app.use(webpackDevMiddleware(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath
|
||||
}));
|
||||
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
}
|
||||
|
||||
_.forEach(files, (fileName) =>
|
||||
{
|
||||
console.log('Loading API ' + fileName + '...');
|
||||
registerAPI(fileName);
|
||||
});
|
||||
});
|
||||
app.use(express.static(path.join(__dirname, 'public', 'dist')));
|
||||
|
||||
|
||||
// Frontend
|
||||
if (isDevelopment)
|
||||
{
|
||||
const compiler = webpack(webpackConfig);
|
||||
// Redirects to make Vue-router URLs less quirky
|
||||
app.get('/c/:code', (req, res) => { res.redirect(301, '/#/c/' + req.params.code) });
|
||||
app.get('/admin', (req, res) => { res.redirect(301, '/#/admin/') });
|
||||
|
||||
app.use(webpackDevMiddleware(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath
|
||||
}));
|
||||
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
var server = app.listen(config.port, () => console.log('Recv running on port ' + server.address().port));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public', 'dist')));
|
||||
|
||||
|
||||
// Redirects to make Vue-router URLs less quirky
|
||||
app.get('/c/:code', (req, res) => { res.redirect(301, '/#/c/' + req.params.code) });
|
||||
app.get('/admin', (req, res) => { res.redirect(301, '/#/admin/') });
|
||||
|
||||
|
||||
var server = app.listen(config.port, () => console.log('Recv running on port ' + server.address().port));
|
||||
})();
|
|
@ -0,0 +1,54 @@
|
|||
const config = require('../../config');
|
||||
const express = require('Express');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, onVerified)
|
||||
{
|
||||
if (!req.headers.authorization || req.headers.authorization.split(' ')[0] !== 'Bearer')
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
var token = req.headers.authorization.split(' ')[1];
|
||||
jwt.verify(token, config.jwtSecret, async (err, decoded) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoded.userId)
|
||||
await onVerified(decoded);
|
||||
else
|
||||
res.sendStatus(400);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
var router = express.Router();
|
||||
|
||||
router.get('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, async (decoded) =>
|
||||
{
|
||||
res.send(await repository.codes.getCodes(decoded.userId));
|
||||
});
|
||||
}));
|
||||
|
||||
return router;
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
const config = require('../../config');
|
||||
const express = require('Express');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
module.exports = (app, repository) =>
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
app.post('/token/upload', async (req, res) =>
|
||||
var router = express.Router();
|
||||
|
||||
router.post('/upload', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.body.code)
|
||||
{
|
||||
|
@ -17,7 +21,7 @@ module.exports = (app, repository) =>
|
|||
{
|
||||
jwt.sign({
|
||||
code: req.body.code,
|
||||
userId: userId
|
||||
codeUserId: userId
|
||||
}, config.jwtSecret, (err, token) =>
|
||||
{
|
||||
if (err)
|
||||
|
@ -28,5 +32,33 @@ module.exports = (app, repository) =>
|
|||
}
|
||||
else
|
||||
res.sendStatus(403);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/login', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.body.username || !req.body.password)
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.getLoginUser(req.body.username, req.body.password);
|
||||
if (user !== null)
|
||||
{
|
||||
jwt.sign({
|
||||
userId: user.id
|
||||
}, config.jwtSecret, (err, token) =>
|
||||
{
|
||||
if (err)
|
||||
res.sendStatus(500);
|
||||
else
|
||||
res.send(token);
|
||||
});
|
||||
}
|
||||
else
|
||||
res.sendStatus(403);
|
||||
}));
|
||||
|
||||
return router;
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
const config = require('../../config');
|
||||
const express = require('express');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
function checkAuthorization(req, res, onVerified)
|
||||
async function checkAuthorization(req, res, onVerified)
|
||||
{
|
||||
if (!req.headers.authorization || req.headers.authorization.split(' ')[0] !== 'Bearer')
|
||||
{
|
||||
|
@ -12,24 +13,37 @@ function checkAuthorization(req, res, onVerified)
|
|||
}
|
||||
|
||||
var token = req.headers.authorization.split(' ')[1];
|
||||
jwt.verify(token, config.jwtSecret, (err, decoded) =>
|
||||
jwt.verify(token, config.jwtSecret, async (err, decoded) =>
|
||||
{
|
||||
if (err)
|
||||
try
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
onVerified(decoded);
|
||||
if (decoded.codeUserId)
|
||||
await onVerified(decoded);
|
||||
else
|
||||
res.sendStatus(400);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = (app, repository) =>
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
var router = express.Router();
|
||||
|
||||
// Upload API
|
||||
app.post('/complete', (req, res) =>
|
||||
router.post('/complete', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.body.files)
|
||||
{
|
||||
|
@ -37,24 +51,25 @@ module.exports = (app, repository) =>
|
|||
return;
|
||||
}
|
||||
|
||||
checkAuthorization(req, res, async (decoded) =>
|
||||
await checkAuthorization(req, res, async (decoded) =>
|
||||
{
|
||||
var expiration = null; // TODO set expiration properties
|
||||
var uploadId = await repository.uploads.addUpload(decoded.userId, req.body.files, expiration);
|
||||
var uploadId = await repository.uploads.addUpload(decoded.codeUserId, req.body.files, expiration);
|
||||
res.send({ id: uploadId });
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
// Tus upload
|
||||
const uploadApp = express();
|
||||
uploadApp.all('*', (req, res) =>
|
||||
uploadApp.all('*', asyncHandler(async (req, res) =>
|
||||
{
|
||||
checkAuthorization(req, res, (decoded) =>
|
||||
await checkAuthorization(req, res, async (decoded) =>
|
||||
{
|
||||
tusServer.handle(req, res);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
app.use('/upload', uploadApp);
|
||||
router.use('/upload', uploadApp);
|
||||
return router;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
const AuthTokens = {
|
||||
ManageUsers: 'manageUsers'
|
||||
}
|
||||
|
||||
var all = [];
|
||||
for (var key in AuthTokens)
|
||||
{
|
||||
if (AuthTokens.hasOwnProperty(key))
|
||||
all.push(AuthTokens[key]);
|
||||
}
|
||||
|
||||
AuthTokens.all = all;
|
||||
module.exports = AuthTokens;
|
|
@ -1,4 +1,21 @@
|
|||
class CodesRepository
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
class Code
|
||||
{
|
||||
constructor(values)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
self.id = values.id || values._id || null;
|
||||
self.userId = values.userId || null;
|
||||
self.created = values.created || new Date();
|
||||
self.expiration = values.expiration || null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CodeRepository
|
||||
{
|
||||
constructor(store)
|
||||
{
|
||||
|
@ -7,7 +24,7 @@ class CodesRepository
|
|||
}
|
||||
|
||||
|
||||
async init()
|
||||
init()
|
||||
{
|
||||
var self = this;
|
||||
|
||||
|
@ -31,7 +48,7 @@ class CodesRepository
|
|||
}
|
||||
|
||||
|
||||
async findCodeUserId(code)
|
||||
findCodeUserId(code)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
|
@ -76,7 +93,28 @@ class CodesRepository
|
|||
maxTimeout: 0
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getCodes(userId)
|
||||
{
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.find({ userId: userId }, (err, docs) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(_.map(docs, (doc) => new Code(doc)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = CodesRepository;
|
||||
module.exports = {
|
||||
Code,
|
||||
CodeRepository
|
||||
}
|
|
@ -2,14 +2,13 @@
|
|||
const _ = require('lodash');
|
||||
const path = require('path');
|
||||
const mkdirp = require('mkdirp');
|
||||
const bcrypt = require('bcrypt');
|
||||
const Datastore = require('nedb');
|
||||
const shortid = require('shortid');
|
||||
const retry = require('async-retry');
|
||||
|
||||
const UsersRepository = require('./users');
|
||||
const CodesRepository = require('./codes');
|
||||
const UploadsRepository = require('./uploads');
|
||||
const UserRepository = require('./user').UserRepository;
|
||||
const CodeRepository = require('./code').CodeRepository;
|
||||
const UploadRepository = require('./upload').UploadRepository;
|
||||
|
||||
|
||||
class Repository
|
||||
|
@ -21,7 +20,7 @@ class Repository
|
|||
}
|
||||
|
||||
|
||||
async load()
|
||||
load()
|
||||
{
|
||||
var self = this;
|
||||
|
||||
|
@ -38,7 +37,7 @@ class Repository
|
|||
return store;
|
||||
};
|
||||
|
||||
mkdirp(self.config.path, async (err) =>
|
||||
mkdirp(self.config.path, (err) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
|
@ -46,12 +45,13 @@ class Repository
|
|||
return;
|
||||
}
|
||||
|
||||
self.users = new UsersRepository(initStore('users.db'));
|
||||
self.codes = new CodesRepository(initStore('codes.db'));
|
||||
self.uploads = new UploadsRepository(initStore('uploads.db'));
|
||||
self.users = new UserRepository(initStore('user.db'));
|
||||
self.codes = new CodeRepository(initStore('code.db'));
|
||||
self.uploads = new UploadRepository(initStore('upload.db'));
|
||||
|
||||
await self.users.init();
|
||||
resolve();
|
||||
self.users.init()
|
||||
.then(() => { resolve() })
|
||||
.catch((e) => { reject(e); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class UploadsRepository
|
||||
class UploadRepository
|
||||
{
|
||||
constructor(store)
|
||||
{
|
||||
|
@ -7,7 +7,7 @@ class UploadsRepository
|
|||
}
|
||||
|
||||
|
||||
async init()
|
||||
init()
|
||||
{
|
||||
var self = this;
|
||||
|
||||
|
@ -31,7 +31,7 @@ class UploadsRepository
|
|||
}
|
||||
|
||||
|
||||
async addUpload(userId, files, expiration)
|
||||
addUpload(userId, files, expiration)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
|
@ -67,4 +67,6 @@ class UploadsRepository
|
|||
}
|
||||
|
||||
|
||||
module.exports = UploadsRepository;
|
||||
module.exports = {
|
||||
UploadRepository
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
const AuthTokens = require('../authtokens');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
|
||||
class User
|
||||
{
|
||||
constructor(values)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
self.id = values.id || values._id || null;
|
||||
self.username = values.username || null;
|
||||
self.password = values.password || null;
|
||||
self.email = values.email || null;
|
||||
self.auth = values.auth || [];
|
||||
|
||||
if (values.hasOwnProperty('active'))
|
||||
self.active = (values.active === true);
|
||||
else
|
||||
self.active = true;
|
||||
|
||||
self.created = values.created || new Date();
|
||||
self.createdByUserId = values.createdByUserId || null;
|
||||
}
|
||||
|
||||
hasAuth(token)
|
||||
{
|
||||
return self.auth.includes(token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UserRepository
|
||||
{
|
||||
constructor(store)
|
||||
{
|
||||
var self = this;
|
||||
self.store = store;
|
||||
}
|
||||
|
||||
|
||||
init()
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
// Initialize database if empty
|
||||
self.store.count({}, (err, count) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
self.addUser(new User({
|
||||
username: 'admin',
|
||||
password: 'changeme',
|
||||
auth: AuthTokens.all
|
||||
}))
|
||||
.then(() => { resolve() })
|
||||
.catch((e) => { reject(e); });
|
||||
}
|
||||
else
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getLoginUser(username, password)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.findOne({ username: username, active: true }, (err, doc) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc == null)
|
||||
{
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bcrypt.compare(password, doc.hashedPassword, (err, res) =>
|
||||
{
|
||||
if (err)
|
||||
reject(err)
|
||||
else
|
||||
resolve(res ? new User(doc) : null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addUser(user)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
bcrypt.hash(user.password, 10, function(err, hash)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
self.store.insert({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
hashedPassword: hash,
|
||||
created: user.created,
|
||||
createdByUserId: user.createdByUserId,
|
||||
active: user.active,
|
||||
auth: user.auth
|
||||
}, (err, dbUser) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(dbUser._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
User,
|
||||
UserRepository
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
class UsersRepository
|
||||
{
|
||||
constructor(store)
|
||||
{
|
||||
var self = this;
|
||||
self.store = store;
|
||||
}
|
||||
|
||||
|
||||
async init()
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
// Initialize database if empty
|
||||
self.store.count({}, (err, count) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
self.addUser('admin', null, 'test', null);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async isValidUser(username, password)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.findOne({ username: username }, (err, doc) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc == null)
|
||||
{
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bcrypt.compare(password, doc.password, (err, res) =>
|
||||
{
|
||||
if (err)
|
||||
reject(err)
|
||||
else
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async addUser(username, email, password, createdByUserId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
bcrypt.hash(password, 10, function(err, hash)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = {
|
||||
username: username,
|
||||
email: email,
|
||||
password: hash,
|
||||
created: new Date(),
|
||||
createdByUserId: createdByUserId
|
||||
};
|
||||
|
||||
self.store.insert(user, (err, dbUser) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(dbUser._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = UsersRepository;
|
|
@ -20,6 +20,7 @@
|
|||
"body-parser": "^1.18.2",
|
||||
"debug": "^3.1.0",
|
||||
"express": "^4.16.2",
|
||||
"express-async-handler": "^1.1.2",
|
||||
"jsonwebtoken": "^8.1.1",
|
||||
"lodash": "^4.17.5",
|
||||
"mkdirp": "^0.5.1",
|
||||
|
|
|
@ -1,10 +1,33 @@
|
|||
<template>
|
||||
<div id="login">
|
||||
Login.
|
||||
<form class="pure-form pure-form-stacked" @submit.prevent="login">
|
||||
<fieldset class="pure-group">
|
||||
<input type="text" class="pure-input-1-2" v-model="username" :placeholder="$t('landing.invitePlaceholder')">
|
||||
<input type="password" class="pure-input-1-2" v-model="password" :placeholder="$t('landing.invitePlaceholder')">
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="pure-button pure-button-primary" :disabled="username.trim() == '' || password.trim() == '' || checking">{{ $t(checking ? 'landing.inviteButtonChecking' : 'landing.inviteButton') }} <span v-if="checking"><i class="fas fa-spinner fa-pulse"></i></span></button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: () =>
|
||||
{
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
checking: false
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
login()
|
||||
{
|
||||
alert('TODO');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue