diff --git a/config.example.js b/config.example.js index f96da75..ed76327 100644 --- a/config.example.js +++ b/config.example.js @@ -4,9 +4,16 @@ module.exports = { port: 3000, nodeModulesPath: path.join(__dirname, 'node_modules'), - userDatabasePath: path.join(__dirname, 'data', 'users'), - fileUploadPath: path.join(__dirname, 'data', 'files'), - fileUploadPublicPath: '/files', + + database: { + path: path.join(__dirname, 'data'), + autocompactionInterval: 60000 + }, + + fileUpload: { + path: path.join(__dirname, 'data', 'files'), + url: '/files' + }, jwtSecret: 'change me to a random generated string' }; \ No newline at end of file diff --git a/index.js b/index.js index e85436f..273b113 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ 'use strict' const config = require('./config'); -const JsonUserDatabase = require('./lib/JsonUserDatabase'); +const Repository = require('./lib/repository'); const _ = require('lodash'); const express = require('express'); @@ -15,20 +15,6 @@ const webpackDevMiddleware = require('webpack-dev-middleware'); const webpackHotMiddleware = require('webpack-hot-middleware'); const webpackConfig = require('./webpack.config.js'); -/* -function metadataToObject(stringValue) -{ - const keyValuePairList = stringValue.split(','); - - return _.reduce(keyValuePairList , (metadata, keyValuePair) => { - let [key, base64Value] = keyValuePair.split(' '); - metadata[key] = new Buffer(base64Value, "base64").toString("ascii"); - - return metadata; - }, {}); -} -*/ - function checkAuthorization(req, res, onVerified) { @@ -56,35 +42,15 @@ function checkAuthorization(req, res, onVerified) { const isDevelopment = process.env.NODE_ENV !== 'production'; - const userDatabase = new JsonUserDatabase(config.userDatabasePath); - await userDatabase.load(); + const repository = new Repository(config.database); + const tusServer = new tus.Server(); tusServer.datastore = new tus.FileStore({ - path: config.fileUploadPublicPath, - directory: config.fileUploadPath + path: config.fileUpload.url, + directory: config.fileUpload.path }); - /* - tusServer.on(tus.EVENTS.EVENT_UPLOAD_COMPLETE, (event) => - { - console.log(event); - - const metadata = metadataToObject(event.file.upload_metadata); - jwt.verify(metadata.token, config.jwtSecret, (err, decoded) => - { - if (err) - return; - - const filePath = path.join(config.fileUploadPath, event.file.id); - console.log(filePath); - - // TODO save metadata for file and notify people - }); - }); - */ - - const app = express(); app.use(bodyParser.urlencoded({ extended: true })); @@ -100,10 +66,12 @@ function checkAuthorization(req, res, onVerified) return; } - if (await userDatabase.isValidCode(req.body.code)) + var userId = await repository.findCodeUserId(req.body.code); + if (userId !== null) { jwt.sign({ - code: req.body.code + code: req.body.code, + userId: userId }, config.jwtSecret, (err, token) => { if (err) @@ -126,10 +94,12 @@ function checkAuthorization(req, res, onVerified) return; } - checkAuthorization(req, res, (decoded) => + checkAuthorization(req, res, async (decoded) => { - console.log(req.body.files); - // TODO save set + console.log('1'); + var uploadId = await repository.addUpload(decoded.userId, req.body.files); + console.log(uploadId); + res.send({ id: uploadId }); }); }); diff --git a/lib/JsonUserDatabase.js b/lib/JsonUserDatabase.js deleted file mode 100644 index b4b2ebf..0000000 --- a/lib/JsonUserDatabase.js +++ /dev/null @@ -1,91 +0,0 @@ -const debug = require('debug')('recv:JsonUserDatabase'); -const uuidv4 = require('uuid/v4'); -const fs = require('mz/fs'); -const mkdir = require('mkdir-promise'); -const path = require('path'); - - -class JsonUserDatabase -{ - constructor(path) - { - this.path = path; - - this.codes = {}; - this.users = {}; - } - - - async load() - { - debug('loading database from ' + this.path); - - try - { - var files = await fs.readdir(this.path); - } - catch(err) - { - if (err.code == 'ENOENT') - // Path does not exist, no worries - files = null; - else - throw err; - } - - if (!files) - return; - - for (var i = 0; i < files.length; i++) - { - var fullPath = path.join(this.path, files[i]); - var stats = await fs.lstat(fullPath); - - if (stats.isFile()) - { - debug('loading ' + fullPath); - try - { - var userInfo = JSON.parse(await fs.readFile(fullPath)); - if (userInfo.type !== 'user') - throw new Error('unsupported file type: ' + userInfo.type); - - this.users[userInfo.uid] = userInfo; - } - catch (err) - { - console.error('error while loading file ' + fullPath + ', skipped:'); - console.error(err); - } - } - } - - debug(Object.keys(this.users).length + ' user(s) loaded'); - } - - - addUser(info) - { - var userId = uuidv4(); - - // TODO add user - // TODO save file - - return userId; - } - - - isValidCode(code) - { - debug('validating code: ' + code); - - // TODO check code - var valid = true; - - debug('valid = ' + valid); - return valid; - } -} - - -module.exports = JsonUserDatabase; \ No newline at end of file diff --git a/lib/repository.js b/lib/repository.js new file mode 100644 index 0000000..f88bdf6 --- /dev/null +++ b/lib/repository.js @@ -0,0 +1,208 @@ +//const debug = require('debug')('recv:users'); +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') + + +class Repository +{ + constructor(config) + { + var self = this; + + const initStore = (filename) => + { + var store = new Datastore({ + filename: path.join(config.path, filename), + autoload: true + }); + + store.persistence.setAutocompactionInterval(config.autocompactionInterval); + return store; + }; + + mkdirp(config.path, (err) => + { + if (err) + { + console.log(err); + return; + } + + self.db = { + users: initStore('users.db'), + codes: initStore('codes.db'), + uploads: initStore('uploads.db') + }; + + + // Initialize database if empty + self.db.users.count({}, (err, count) => + { + if (err) + { + console.log(err); + return; + } + + if (count == 0) + self.addUser('admin', null, 'test'); + }); + }); + } + + + async isValidUser(username, password) + { + var self = this; + + return new Promise((resolve, reject) => + { + self.db.users.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) + { + 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 + }; + + self.db.users.insert(user, (err, dbUser) => + { + if (err) + { + reject(err); + return; + } + + resolve(dbUser._id); + }); + }); + }); + } + + + async findCodeUserId(code) + { + var self = this; + + return new Promise((resolve, reject) => + { + self.db.codes.findOne({ _id: code }, (err, doc) => + { + if (err) + { + reject(err); + return; + } + + resolve(doc !== null ? doc.userId : null); + }); + }); + } + + + async addCode(userId, expiration) + { + var self = this; + + return await retry(async bail => + { + var code = shortid.generate(); + + if ((await self.findCodeUserId(code)) !== null) + throw new Error('Code ' + code + ' already exists'); + + self.db.codes.insert({ + _id: code, + userId: userId, + expiration: expiration + }) + + return code; + }, { + retries: 100, + minTimeout: 0, + maxTimeout: 0 + }); + } + + + async addUpload(userId, files) + { + var self = this; + + return new Promise((resolve, reject) => + { + var upload = { + files: _.map(_.filter(files, + (file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')), + (file) => { return { id: file.id, name: file.name } }) + }; + console.log(upload); + + if (upload.files.length) + { + self.db.uploads.insert(upload, (err, dbUpload) => + { + console.log(dbUpload); + if (err) + { + reject(err); + return; + } + + resolve(dbUpload._id); + }); + } + else + { + reject(); + } + }); + } +} + + +module.exports = Repository; \ No newline at end of file diff --git a/package.json b/package.json index 5b4f908..eb6ed2c 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,18 @@ "author": "Mark van Renswoude ", "license": "Unlicense", "dependencies": { + "async-retry": "^1.2.1", + "bcrypt": "^1.0.3", "body-parser": "^1.18.2", "debug": "^3.1.0", "express": "^4.16.2", "jsonwebtoken": "^8.1.1", - "mkdir-promise": "^1.0.0", - "mz": "^2.7.0", + "lodash": "^4.17.5", + "mkdirp": "^0.5.1", + "nedb": "^1.8.0", "npm": "^5.7.1", - "tus-node-server": "^0.2.10", - "uuid": "^3.2.1", - "lodash": "^4.17.5" + "shortid": "^2.2.8", + "tus-node-server": "^0.2.10" }, "devDependencies": { "axios": "^0.18.0", diff --git a/public/src/route/Landing.vue b/public/src/route/Landing.vue index ae93fe0..4fca8e9 100644 --- a/public/src/route/Landing.vue +++ b/public/src/route/Landing.vue @@ -50,7 +50,7 @@ export default { self.code.checking = true; axios.post('/token/upload', { - code: self.code + code: self.code.value }) .then((response) => {