From 08b81d5ff43a3c0f3a053de0c210cfdc271dc13d Mon Sep 17 00:00:00 2001 From: "Lukas F. Hartmann" Date: Thu, 12 Apr 2018 16:38:48 +0200 Subject: [PATCH] port most backend functionality, further cleanups, basic electron support --- app.js | 189 +++------------------- helpers/mailer.js | 8 +- helpers/websockets.js | 2 +- middlewares/i18n.js | 4 +- models/db.js | 47 +++++- package.json | 48 +----- public/javascripts/spacedeck_account.js | 10 +- public/javascripts/spacedeck_routes.js | 1 - public/javascripts/spacedeck_vue.js | 2 +- routes/api/space_digest.js | 2 - routes/api/space_exports.js | 3 - routes/api/space_messages.js | 20 +-- routes/api/spaces.js | 49 +++--- routes/api/users.js | 119 +++++--------- routes/root.js | 1 - spacedeck.js | 169 +++++++++++++++++++ views/partials/account.html | 10 +- views/partials/tool/toolbar-elements.html | 15 +- views/spacedeck.html | 4 + 19 files changed, 331 insertions(+), 372 deletions(-) create mode 100644 spacedeck.js diff --git a/app.js b/app.js index 1f21be1..73d7fd5 100644 --- a/app.js +++ b/app.js @@ -1,172 +1,33 @@ -"use strict"; +const spacedeck = require('./spacedeck') -const db = require('./models/db.js'); -require("log-timestamp"); +const electron = require('electron') +const electronApp = electron.app +const BrowserWindow = electron.BrowserWindow +let mainWindow -const config = require('config'); -const redis = require('./helpers/redis'); -const websockets = require('./helpers/websockets'); - -const http = require('http'); -const path = require('path'); - -const _ = require('underscore'); -const favicon = require('serve-favicon'); -const logger = require('morgan'); -const cookieParser = require('cookie-parser'); -const bodyParser = require('body-parser'); - -//const mongoose = require('mongoose'); - -const swig = require('swig'); -const i18n = require('i18n-2'); -const helmet = require('helmet'); - -const express = require('express'); -const app = express(); -const serveStatic = require('serve-static'); - -const isProduction = app.get('env') === 'production'; - -console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")"); - -app.use(logger(isProduction ? 'combined' : 'dev')); - -i18n.expressBind(app, { - locales: ["en", "de", "fr"], - defaultLocale: "en", - cookieName: "spacedeck_locale", - devMode: (app.get('env') == 'development') -}); - -swig.setDefaults({ - varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js -}); - -swig.setFilter('cdn', function(input, idx) { - return input; -}); - -app.engine('html', swig.renderFile); -app.set('view engine', 'html'); - -if (isProduction) { - app.set('views', path.join(__dirname, 'build', 'views')); - app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png'))); - app.use(express.static(path.join(__dirname, 'build', 'assets'))); -} else { - app.set('views', path.join(__dirname, 'views')); - app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png'))); - app.use(express.static(path.join(__dirname, 'public'))); +function createWindow () { + mainWindow = new BrowserWindow({width: 1200, height: 700}) + mainWindow.loadURL("http://localhost:9666") + mainWindow.on('closed', function () { + mainWindow = null + }) } -app.use(bodyParser.json({ - limit: '50mb' -})); +electronApp.on('ready', createWindow) -app.use(bodyParser.urlencoded({ - extended: false, - limit: '50mb' -})); - -app.use(cookieParser()); -app.use(helmet.frameguard()) -app.use(helmet.xssFilter()) -app.use(helmet.hsts({ - maxAge: 7776000000, - includeSubdomains: true -})) -app.disable('x-powered-by'); -app.use(helmet.noSniff()) - -//app.use(require("./middlewares/error_helpers")); -app.use(require("./middlewares/session")); -//app.use(require("./middlewares/cors")); -//app.use(require("./middlewares/i18n")); -app.use("/api", require("./middlewares/api_helpers")); -app.use('/api/spaces/:id', require("./middlewares/space_helpers")); -app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers")); - -app.use('/api/users', require('./routes/api/users')); -app.use('/api/memberships', require('./routes/api/memberships')); - -const spaceRouter = require('./routes/api/spaces'); -app.use('/api/spaces', spaceRouter); - -spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts')); -spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships')); -spaceRouter.use('/:id/messages', require('./routes/api/space_messages')); -spaceRouter.use('/:id/digest', require('./routes/api/space_digest')); -spaceRouter.use('/:id', require('./routes/api/space_exports')); - -app.use('/api/sessions', require('./routes/api/sessions')); -//app.use('/api/webgrabber', require('./routes/api/webgrabber')); -app.use('/', require('./routes/root')); - -if (config.get('storage_local_path')) { - app.use('/storage', serveStatic(config.get('storage_local_path')+"/"+config.get('storage_bucket'), { - maxAge: 24*3600 - })); -} - -// catch 404 and forward to error handler -//app.use(require('./middlewares/404')); -if (app.get('env') == 'development') { - app.set('view cache', false); - swig.setDefaults({cache: false}); -} else { - app.use(require('./middlewares/500')); -} - -module.exports = app; - -// CONNECT TO DATABASE -db.init(); - -// START WEBSERVER -const port = 9666; - -const server = http.Server(app).listen(port, () => { - - if ("send" in process) { - process.send('online'); +// Quit when all windows are closed. +electronApp.on('window-all-closed', function () { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + electronApp.quit() } +}) -}).on('listening', () => { - - const host = server.address().address; - const port = server.address().port; - console.log('Spacedeck Open listening at http://%s:%s', host, port); - -}).on('error', (error) => { - - if (error.syscall !== 'listen') { - throw error; +electronApp.on('activate', function () { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() } - - const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -}); - -//WEBSOCKETS & WORKER -websockets.startWebsockets(server); -redis.connectRedis(); - -process.on('message', (message) => { - console.log("Process message:", message); - if (message === 'shutdown') { - console.log("Exiting Spacedeck."); - process.exit(0); - } -}); +}) diff --git a/helpers/mailer.js b/helpers/mailer.js index 15545d0..835dfb9 100644 --- a/helpers/mailer.js +++ b/helpers/mailer.js @@ -1,7 +1,7 @@ 'use strict'; var swig = require('swig'); -var AWS = require('aws-sdk'); +//var AWS = require('aws-sdk'); module.exports = { sendMail: (to_email, subject, body, options) => { @@ -29,9 +29,9 @@ module.exports = { options: options }); - if (process.env.NODE_ENV === 'development') { + //if (process.env.NODE_ENV === 'development') { console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext); - } else { + /*} else { AWS.config.update({region: 'eu-west-1'}); var ses = new AWS.SES(); @@ -56,6 +56,6 @@ module.exports = { if (err) console.error("Error sending email:", err); else console.log("Email sent."); }); - } + }*/ } }; diff --git a/helpers/websockets.js b/helpers/websockets.js index 23caebd..394e45c 100644 --- a/helpers/websockets.js +++ b/helpers/websockets.js @@ -8,7 +8,7 @@ const config = require('config'); const WebSocketServer = require('ws').Server; -const RedisConnection = require('ioredis'); +//const RedisConnection = require('ioredis'); const async = require('async'); const _ = require("underscore"); const crypto = require('crypto'); diff --git a/middlewares/i18n.js b/middlewares/i18n.js index f330312..32ce411 100644 --- a/middlewares/i18n.js +++ b/middlewares/i18n.js @@ -10,8 +10,8 @@ module.exports = (req, res, next) => { req.i18n.setLocaleFromCookie(); } - if (req.user && req.user.preferences.language) { - req.i18n.setLocale(req.user.preferences.language); + if (req.user && req.user.prefs_language) { + req.i18n.setLocale(req.user.prefs_language); } next(); } diff --git a/models/db.js b/models/db.js index 488ec15..00aa0a6 100644 --- a/models/db.js +++ b/models/db.js @@ -198,6 +198,48 @@ module.exports = { }), init: function() { + User = this.User; + Session = this.Session; + Space = this.Space; + Artifact = this.Artifact; + Message = this.Message; + Membership = this.Membership; + + Space.belongsTo(User, { + foreignKey: { + name: 'creator_id' + }, + as: 'creator' + }); + + Artifact.belongsTo(User, { + foreignKey: { + name: 'user_id' + }, + as: 'user' + }); + + Artifact.belongsTo(Space, { + foreignKey: { + name: 'space_id' + }, + as: 'space' + }); + + Message.belongsTo(User, { + foreignKey: { + name: 'user_id' + }, + as: 'user' + }); + + Message.belongsTo(Space, { + foreignKey: { + name: 'space_id' + }, + as: 'space' + }); + sequelize.sync(); }, @@ -222,7 +264,6 @@ module.exports = { }); } else { // reached the top - var role = prevRole; space.memberships = currentMemberships; @@ -252,10 +293,10 @@ module.exports = { }, findUserBySessionToken: (token, cb) => { - db.Session.findOne({where: {token: token}}) + Session.findOne({where: {token: token}}) .then(session => { if (!session) cb(null, null) - else db.User.findOne({where: {_id: session.user_id}}) + else User.findOne({where: {_id: session.user_id}}) .then(user => { cb(null, user) }) diff --git a/package.json b/package.json index 21cfd46..f2af01e 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { - "start": "nodemon -e .js,.html bin/www", - "test": "mocha" + "start": "electron ." }, "engines": { "node": ">=7.8.0" @@ -12,48 +11,26 @@ "dependencies": { "archiver": "1.3.0", "async": "2.3.0", - "aws-sdk": "2.39.0", "basic-auth": "1.1.0", "bcryptjs": "2.4.3", "body-parser": "~1.17.1", "cheerio": "0.22.0", "config": "1.25.1", "cookie-parser": "~1.4.3", - "csurf": "1.9.0", - "debug": "~2.6.3", "electron": "^1.8.4", "execSync": "latest", "express": "~4.13.0", - "extract-zip": "^1.6.6", "file-type": "^7.6.0", "glob": "7.1.1", "gm": "1.23.0", - "googleapis": "18.0.0", - "gulp": "^3.9.1", - "gulp-concat": "2.6.0", - "gulp-express": "0.3.0", - "gulp-nodemon": "*", - "gulp-sass": "^2.0.3", - "gulp-uglify": "^1.5.1", - "gulp-util": "^3.0.6", "helmet": "^3.5.0", "i18n-2": "0.6.3", - "ioredis": "2.5.0", - "lodash": "^4.3.0", "log-timestamp": "latest", - "md5": "2.2.1", - "mime-types": "^2.1.18", + "morgan": "1.8.1", "mock-aws-s3": "^2.6.0", "moment": "^2.19.3", - "mongoose": "4.9.3", - "morgan": "1.8.1", "node-phantom-simple": "2.2.4", - "node-sass-middleware": "0.11.0", - "pdfkit": "0.8.0", "phantomjs-prebuilt": "2.1.14", - "pm2": "latest", - "qr-image": "3.2.0", - "raven": "1.2.0", "read-chunk": "^2.1.0", "request": "2.81.0", "sanitize-html": "^1.11.1", @@ -66,29 +43,10 @@ "underscore": "1.8.3", "uuid": "^3.2.1", "validator": "7.0.0", - "weak": "1.0.1", "ws": "2.2.3" }, - "devDependencies": { - "express": "^4.13.3", - "gulp": "^3.9.1", - "gulp-clean": "^0.3.2", - "gulp-concat": "^2.6.0", - "gulp-express": "^0.3.0", - "gulp-fingerprint": "^0.3.2", - "gulp-nodemon": "^2.0.4", - "gulp-rev": "^7.1.2", - "gulp-rev-all": "^0.9.7", - "gulp-rev-replace": "^0.4.3", - "gulp-sass": "^3.1.0", - "gulp-uglify": "^2.1.2", - "nodemon": "1.11.0", - "should": "^11.2.1", - "supertest": "^3.0.0", - "winston": "^2.3.1" - }, + "main": "app.js", "description": "", - "main": "Gulpfile.js", "directories": {}, "repository": { "type": "git", diff --git a/public/javascripts/spacedeck_account.js b/public/javascripts/spacedeck_account.js index d66d9c4..1c0b5d1 100644 --- a/public/javascripts/spacedeck_account.js +++ b/public/javascripts/spacedeck_account.js @@ -13,19 +13,17 @@ SpacedeckAccount = { methods: { show_account: function(user) { this.activate_dropdown('account'); - this.load_subscription(); - this.load_billing(); }, account_save_user_digest: function(val) { - this.user.preferences.daily_digest = val; - this.save_user(function(){ + this.user.prefs_email_digest = val; + this.save_user(function() { }); }, account_save_user_notifications: function(val) { - this.user.preferences.email_notifications = val; - this.save_user(function(){ + this.user.prefs_email_notifications = val; + this.save_user(function() { }); }, diff --git a/public/javascripts/spacedeck_routes.js b/public/javascripts/spacedeck_routes.js index eda4066..e294358 100644 --- a/public/javascripts/spacedeck_routes.js +++ b/public/javascripts/spacedeck_routes.js @@ -170,7 +170,6 @@ var SpacedeckRoutes = { location.href = "/"; } else { this.active_view = "account"; - this.load_subscription(); } }.bind(this) } diff --git a/public/javascripts/spacedeck_vue.js b/public/javascripts/spacedeck_vue.js index ead97d7..141cb1a 100644 --- a/public/javascripts/spacedeck_vue.js +++ b/public/javascripts/spacedeck_vue.js @@ -158,7 +158,7 @@ function boot_spacedeck() { }); } -$(document).ready(function(){ +document.addEventListener("DOMContentLoaded",function() { window.smoke = smoke; window.alert = smoke.alert; diff --git a/routes/api/space_digest.js b/routes/api/space_digest.js index f13af45..cbdf490 100644 --- a/routes/api/space_digest.js +++ b/routes/api/space_digest.js @@ -6,12 +6,10 @@ require('../../models/db'); var async = require('async'); var fs = require('fs'); var _ = require("underscore"); -var mongoose = require("mongoose"); var request = require('request'); var url = require("url"); var path = require("path"); var crypto = require('crypto'); -var qr = require('qr-image'); var glob = require('glob'); var gm = require('gm'); diff --git a/routes/api/space_exports.js b/routes/api/space_exports.js index b7e74da..2b7b15b 100644 --- a/routes/api/space_exports.js +++ b/routes/api/space_exports.js @@ -2,7 +2,6 @@ var config = require('config'); const db = require('../../models/db'); -var redis = require('../../helpers/redis'); var mailer = require('../../helpers/mailer'); var uploader = require('../../helpers/uploader'); var space_render = require('../../helpers/space-render'); @@ -12,13 +11,11 @@ var async = require('async'); var moment = require('moment'); var fs = require('fs'); var _ = require("underscore"); -var mongoose = require("mongoose"); var archiver = require('archiver'); var request = require('request'); var url = require("url"); var path = require("path"); var crypto = require('crypto'); -var qr = require('qr-image'); var glob = require('glob'); var gm = require('gm'); var sanitizeHtml = require('sanitize-html'); diff --git a/routes/api/space_messages.js b/routes/api/space_messages.js index 51dfbc3..400b78d 100644 --- a/routes/api/space_messages.js +++ b/routes/api/space_messages.js @@ -14,7 +14,6 @@ var phantom = require('../../helpers/phantom'); var async = require('async'); var fs = require('fs'); var _ = require("underscore"); -var mongoose = require("mongoose"); var archiver = require('archiver'); var request = require('request'); var url = require("url"); @@ -52,8 +51,7 @@ var roleMapping = { router.get('/', function(req, res, next) { db.Message.findAll({where:{ space_id: req.space._id - }}) - //.populate('user', userMapping) + }, include: ['user']}) .then(function(messages) { res.status(200).json(messages); }); @@ -65,6 +63,7 @@ router.post('/', function(req, res, next) { if (req.user) { attrs.user = req.user; + attrs.user_id = req.user._id; } else { attrs.user = null; } @@ -72,7 +71,7 @@ router.post('/', function(req, res, next) { var msg = attrs; msg._id = uuidv4(); - db.Message.create(msg, function() { + db.Message.create(msg).then(function() { if (msg.message.length <= 1) return; // TODO reimplement notifications res.distributeCreate("Message", msg); @@ -82,19 +81,12 @@ router.post('/', function(req, res, next) { router.delete('/:message_id', function(req, res, next) { db.Message.findOne({where:{ "_id": req.params.message_id - }}, function(msg) { + }}).then(function(msg) { if (!msg) { res.sendStatus(404); } else { - msg.destroy(function(err) { - if (err) res.status(400).json(err); - else { - if (msg) { - res.distributeDelete("Message", msg); - } else { - res.sendStatus(404); - } - } + msg.destroy().then(function() { + res.distributeDelete("Message", msg); }); } }); diff --git a/routes/api/spaces.js b/routes/api/spaces.js index c61a5b2..57f5704 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -17,7 +17,6 @@ var slug = require('slug'); var fs = require('fs'); var async = require('async'); var _ = require("underscore"); -var mongoose = require("mongoose"); var request = require('request'); var url = require("url"); var path = require("path"); @@ -67,6 +66,7 @@ router.get('/', function(req, res, next) { return m.space_id; }); + // TODO port var q = { "space_type": "folder", "$or": [{ @@ -81,13 +81,11 @@ router.get('/', function(req, res, next) { }] }; - Space - .find(q) - .populate('creator', userMapping) - .exec(function(err, spaces) { - if (err) console.error(err); + db.Space + .findAll({where: q}) + .then(function(spaces) { var updatedSpaces = spaces.map(function(s) { - var spaceObj = s.toObject(); + var spaceObj = s; //.toObject(); return spaceObj; }); @@ -104,18 +102,17 @@ router.get('/', function(req, res, next) { return s.space_type == "folder"; }) var uniqueFolders = _.unique(onlyFolders, (s) => { - return s._id.toString(); + return s._id; }) res.status(200).json(uniqueFolders); - }); }); }); } else if (req.query.search) { db.Membership.findAll({where:{ - user: req.user._id + user_id: req.user._id }}).then(memberships => { var validMemberships = memberships.filter(function(m) { @@ -131,21 +128,15 @@ router.get('/', function(req, res, next) { // TODO FIXME port var q = { where: { - "$or": [{"creator_id": req.user._id}, - {"_id": {"$in": spaceIds}}, - {"parent_space_id": {"$in": spaceIds}}], - name: new RegExp(req.query.search, "i")} - }; + [Op.or]: [{"creator_id": req.user._id}, + {"_id": {[Op.in]: spaceIds}}, + {"parent_space_id": {[Op.in]: spaceIds}}], + name: {[Op.like]: "%"+req.query.search+"%"} + }, include: ['creator']}; db.Space .findAll(q) - //.populate('creator', userMapping) .then(function(spaces) { - if (err) console.error(err); - var updatedSpaces = spaces.map(function(s) { - var spaceObj = s.toObject(); - return spaceObj; - }); res.status(200).json(spaces); }); }); @@ -161,7 +152,7 @@ router.get('/', function(req, res, next) { if (space) { db.getUserRoleInSpace(space, req.user, function(role) { if (role == "none") { - if(space.access_mode == "public") { + if (space.access_mode == "public") { role = "viewer"; } } @@ -170,8 +161,7 @@ router.get('/', function(req, res, next) { db.Space .findAll({where:{ parent_space_id: req.query.parent_space_id - }}) - //.populate('creator', userMapping) + }, include:['creator']}) .then(function(spaces) { res.status(200).json(spaces); }); @@ -214,7 +204,7 @@ router.get('/', function(req, res, next) { }; db.Space - .findAll({where: q}) + .findAll({where: q, include: ['creator']}) .then(function(spaces) { var updatedSpaces = spaces.map(function(s) { var spaceObj = db.spaceToObject(s); @@ -414,15 +404,12 @@ router.delete('/:id', function(req, res, next) { if (req.spaceRole == "admin") { const attrs = req.body; - Space.recursiveDelete(space, function(err) { - if (err) res.status(400).json(err); - else { - res.distributeDelete("Space", space); - } + space.destroy().then(function() { + res.distributeDelete("Space", space); }); } else { res.status(403).json({ - "error": "requires admin status" + "error": "requires admin role" }); } } else { diff --git a/routes/api/users.js b/routes/api/users.js index 4df055f..d7ad410 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -74,14 +74,12 @@ router.post('/', function(req, res) { res.sendStatus(400); }) .then(u => { - console.log("!!! created user:", u); var homeSpace = { _id: uuidv4(), name: req.i18n.__("home"), space_type: "folder", creator_id: u._id }; - db.Space.create(homeSpace) .error(err => { res.sendStatus(400); @@ -108,8 +106,6 @@ router.post('/', function(req, res) { }); }); }; - - console.log("!!! hello !!!"); db.User.findAll({where: {email: email}}) .then(users => { @@ -131,19 +127,15 @@ router.get('/current', function(req, res, next) { }); router.put('/:id', function(req, res, next) { + // TODO explicit whitelisting var user = req.user; - console.log(req.params.id, user._id); if (user._id == req.params.id) { var newAttr = req.body; newAttr.updated_at = new Date(); delete newAttr['_id']; - User.findOneAndUpdate({"_id": user._id}, {"$set": newAttr}, function(err, updatedUser) { - if (err) { - res.sendStatus(400); - } else { - res.status(200).json(updatedUser); - } + db.User.update(newAttr, {where: {"_id": user._id}}).then(function(updatedUser) { + res.status(200).json(newAttr); }); } else { res.sendStatus(403); @@ -161,12 +153,8 @@ router.post('/:id/password', function(req, res, next) { bcrypt.genSalt(10, function(err, salt) { bcrypt.hash(pass, salt, function(err, hash) { user.password_hash = hash; - user.save(function(err){ - if(err){ - res.status(400).json(err); - }else{ - res.sendStatus(204); - } + user.save().then(function() { + res.sendStatus(204); }); }); }); @@ -195,7 +183,7 @@ router.delete('/:id', (req, res, next) => { } } else { user.remove((err) => { - if(err)res.status(400).json(err); + if (err) res.status(400).json(err); else res.sendStatus(204); }); } @@ -239,19 +227,15 @@ router.post('/:user_id/avatar', (req, res, next) => { if (err) res.status(400).json(err); else { user.avatar_thumb_uri = url; - user.save((err, updatedUser) => { - if (err) { - res.sendStatus(400); - } else { - fs.unlink(localResizedFilePath, (err) => { - if (err) { - console.error(err); - res.status(400).json(err); - } else { - res.status(200).json(updatedUser); - } - }); - } + user.save().then(() => { + fs.unlink(localResizedFilePath, (err) => { + if (err) { + console.error(err); + res.status(400).json(err); + } else { + res.status(200).json(user); + } + }); }); } }); @@ -269,31 +253,20 @@ router.post('/feedback', function(req, res, next) { router.post('/password_reset_requests', (req, res, next) => { const email = req.query.email; - User.findOne({"email": email}).exec((err, user) => { - if (err) { - res.status(400).json(err); + db.User.findOne({where: {"email": email}}).then((user) => { + if (user) { + crypto.randomBytes(16, (ex, buf) => { + user.password_reset_token = buf.toString('hex'); + user.save().then(updatedUser => { + mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: { + link: config.endpoint + "/password-confirm/" + user.password_reset_token, + name: req.i18n.__("password_reset_action") + }}); + res.status(201).json({}); + }); + }); } else { - if (user) { - if(user.account_type == "email") { - crypto.randomBytes(16, (ex, buf) => { - user.password_reset_token = buf.toString('hex'); - user.save((err, updatedUser) => { - if (err) res.status(400).json(err); - else { - mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: { - link: config.endpoint + "/password-confirm/" + user.password_reset_token, - name: req.i18n.__("password_reset_action") - }}); - res.status(201).json({}); - } - }); - }); - } else { - res.status(404).json({"error": "error_unknown_email"}); - } - } else { - res.status(404).json({"error": "error_unknown_email"}); - } + res.status(404).json({"error": "error_unknown_email"}); } }); }); @@ -302,29 +275,25 @@ router.post('/password_reset_requests/:confirm_token/confirm', function(req, res var password = req.body.password; User - .findOne({"password_reset_token": req.params.confirm_token}) - .exec((err, user) => { - if (err) { - res.sendStatus(400); - } else { - if(user) { - bcrypt.genSalt(10, (err, salt) => { - bcrypt.hash(password, salt, function(err, hash) { + .findOne({where: {"password_reset_token": req.params.confirm_token}}) + .then((user) => { + if (user) { + bcrypt.genSalt(10, (err, salt) => { + bcrypt.hash(password, salt, function(err, hash) { - user.password_hash = hash; - user.password_token = null; - user.save(function(err, updatedUser){ - if (err) { - res.sendStatus(400); - } else { - res.sendStatus(201); - } - }); + user.password_hash = hash; + user.password_token = null; + user.save(function(err, updatedUser){ + if (err) { + res.sendStatus(400); + } else { + res.sendStatus(201); + } }); }); - } else { - res.sendStatus(404); - } + }); + } else { + res.sendStatus(404); } }); }); diff --git a/routes/root.js b/routes/root.js index d6979b0..7819b1b 100644 --- a/routes/root.js +++ b/routes/root.js @@ -9,7 +9,6 @@ const crypto = require('crypto'); const router = express.Router(); const mailer = require('../helpers/mailer'); const _ = require('underscore'); -const qr = require('qr-image'); router.get('/', (req, res) => { res.render('index', { title: 'Spaces' }); diff --git a/spacedeck.js b/spacedeck.js new file mode 100644 index 0000000..6b841d1 --- /dev/null +++ b/spacedeck.js @@ -0,0 +1,169 @@ +"use strict"; + +const db = require('./models/db.js'); +require("log-timestamp"); + +const config = require('config'); +const redis = require('./helpers/redis'); +const websockets = require('./helpers/websockets'); + +const http = require('http'); +const path = require('path'); + +const _ = require('underscore'); +const favicon = require('serve-favicon'); +const logger = require('morgan'); +const cookieParser = require('cookie-parser'); +const bodyParser = require('body-parser'); + +const swig = require('swig'); +const i18n = require('i18n-2'); +const helmet = require('helmet'); + +const express = require('express'); +const app = express(); +const serveStatic = require('serve-static'); + +const isProduction = app.get('env') === 'production'; + +console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")"); + +app.use(logger(isProduction ? 'combined' : 'dev')); + +i18n.expressBind(app, { + locales: ["en", "de", "fr"], + defaultLocale: "en", + cookieName: "spacedeck_locale", + devMode: (app.get('env') == 'development') +}); + +swig.setDefaults({ + varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js +}); + +swig.setFilter('cdn', function(input, idx) { + return input; +}); + +app.engine('html', swig.renderFile); +app.set('view engine', 'html'); + +if (isProduction) { + app.set('views', path.join(__dirname, 'build', 'views')); + app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png'))); + app.use(express.static(path.join(__dirname, 'build', 'assets'))); +} else { + app.set('views', path.join(__dirname, 'views')); + app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png'))); + app.use(express.static(path.join(__dirname, 'public'))); +} + +app.use(bodyParser.json({ + limit: '50mb' +})); + +app.use(bodyParser.urlencoded({ + extended: false, + limit: '50mb' +})); + +app.use(cookieParser()); +app.use(helmet.frameguard()) +app.use(helmet.xssFilter()) +app.use(helmet.hsts({ + maxAge: 7776000000, + includeSubdomains: true +})) +app.disable('x-powered-by'); +app.use(helmet.noSniff()) + +//app.use(require("./middlewares/error_helpers")); +app.use(require("./middlewares/session")); +//app.use(require("./middlewares/cors")); +app.use(require("./middlewares/i18n")); +app.use("/api", require("./middlewares/api_helpers")); +app.use('/api/spaces/:id', require("./middlewares/space_helpers")); +app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers")); + +app.use('/api/users', require('./routes/api/users')); +app.use('/api/memberships', require('./routes/api/memberships')); + +const spaceRouter = require('./routes/api/spaces'); +app.use('/api/spaces', spaceRouter); + +spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts')); +spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships')); +spaceRouter.use('/:id/messages', require('./routes/api/space_messages')); +spaceRouter.use('/:id/digest', require('./routes/api/space_digest')); +spaceRouter.use('/:id', require('./routes/api/space_exports')); + +app.use('/api/sessions', require('./routes/api/sessions')); +//app.use('/api/webgrabber', require('./routes/api/webgrabber')); +app.use('/', require('./routes/root')); + +if (config.get('storage_local_path')) { + app.use('/storage', serveStatic(config.get('storage_local_path')+"/"+config.get('storage_bucket'), { + maxAge: 24*3600 + })); +} + +// catch 404 and forward to error handler +//app.use(require('./middlewares/404')); +if (app.get('env') == 'development') { + app.set('view cache', false); + swig.setDefaults({cache: false}); +} else { + app.use(require('./middlewares/500')); +} + +module.exports = app; + +// CONNECT TO DATABASE +db.init(); + +// START WEBSERVER +const port = 9666; + +const server = http.Server(app).listen(port, () => { + + if ("send" in process) { + process.send('online'); + } + +}).on('listening', () => { + + const host = server.address().address; + const port = server.address().port; + console.log('Spacedeck Open listening at http://%s:%s', host, port); + +}).on('error', (error) => { + + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +}); + +websockets.startWebsockets(server); +redis.connectRedis(); + +/*process.on('message', (message) => { + console.log("Process message:", message); + if (message === 'shutdown') { + console.log("Exiting Spacedeck."); + process.exit(0); + } +});*/ diff --git a/views/partials/account.html b/views/partials/account.html index ff70cfc..5c20254 100644 --- a/views/partials/account.html +++ b/views/partials/account.html @@ -86,15 +86,15 @@
@@ -104,8 +104,8 @@
- - - +