var fs = require('fs'); var url = require('url'); var rfs = require('rotating-file-stream'); var mqtt = require('mqtt'); var path = require('path'); var http = require('http'); var https = require('https'); var flash = require('connect-flash'); var morgan = require('morgan'); var express = require('express'); var session = require('express-session'); var passport = require('passport'); var mongoose = require('mongoose'); var bodyParser = require('body-parser'); var Measurement = require('./googleMeasurement.js'); var cookieParser = require('cookie-parser'); var BasicStrategy = require('passport-http').BasicStrategy; var LocalStrategy = require('passport-local').Strategy; var SimpleNodeLogger = require('simple-node-logger'); var PassportOAuthBearer = require('passport-http-bearer'); var loggingOptions = { logDirectory: 'log', fileNamePattern:'debug-.log', timestampFormat:'YYYY-MM-DD HH:mm:ss.SSS', dateFormat:'YYYY.MM.DD' }; var logger = SimpleNodeLogger.createRollingFileLogger(loggingOptions); var oauthServer = require('./oauth'); var port = (process.env.VCAP_APP_PORT || process.env.PORT || 3000); var host = (process.env.VCAP_APP_HOST || '0.0.0.0'); var mongo_url = (process.env.MONGO_URL || 'mongodb://localhost/users'); var mqtt_url = (process.env.MQTT_URL || 'mqtt://localhost:1883'); var mqtt_user = (process.env.MQTT_USER || undefined); var mqtt_password = (process.env.MQTT_PASSWORD || undefined); console.log(mqtt_url); var googleAnalyicsTID = process.env.GOOGLE_ANALYTICS_TID; var measurement = new Measurement(googleAnalyicsTID); var mqttClient; var mqttOptions = { reconnectPeriod: 3000, keepAlive: 10, clean: true, clientId: 'webApp_' + Math.random().toString(16).substr(2, 8) }; if (mqtt_user) { mqttOptions.username = mqtt_user; mqttOptions.password = mqtt_password; } mqttClient = mqtt.connect(mqtt_url, mqttOptions); mqttClient.on('error',function(err){ }); mqttClient.on('reconnect', function(){ }); mqttClient.on('connect', function(){ mqttClient.subscribe('response/#'); }); if (process.env.VCAP_SERVICES) { var services = JSON.parse(process.env.VCAP_SERVICES); for (serviceName in services) { if (serviceName.match('^mongo')) { var creds = services[serviceName][0]['credentials']; mongo_url = creds.url; } else { console.log("no database found in vcap_services"); } } } console.log(mongo_url); mongoose.Promise = global.Promise; var mongoose_options = { server: { auto_reconnect:true, autoReconnect: true, reconnectTries: Number.MAX_VALUE, reconnectInterval: 1000, socketOptions: { autoReconnect: true } } }; var mongoose_connection = mongoose.connection; mongoose_connection.on('connecting', function() { console.log('connecting to MongoDB...'); }); mongoose_connection.on('error', function(error) { console.error('Error in MongoDb connection: ' + error); //mongoose.disconnect(); }); mongoose_connection.on('connected', function() { console.log('MongoDB connected!'); }); mongoose_connection.once('open', function() { console.log('MongoDB connection opened!'); }); mongoose_connection.on('reconnected', function () { console.log('MongoDB reconnected!'); }); mongoose_connection.on('disconnected', function() { console.log('MongoDB disconnected!'); }); mongoose.connect(mongo_url, mongoose_options); var Account = require('./models/account'); var oauthModels = require('./models/oauth'); var Devices = require('./models/devices'); var Topics = require('./models/topics'); var LostPassword = require('./models/lostPassword'); Account.findOne({username: mqtt_user}, function(error, account){ if (!error && !account) { Account.register(new Account({username: mqtt_user, email: '', mqttPass: '', superuser: 1}), mqtt_password, function(err, account){ var topics = new Topics({topics: [ 'command/' +account.username+'/#', 'presence/' + account.username + '/#', 'response/' + account.username + '/#' ]}); topics.save(function(err){ if (!err){ var s = Buffer.from(account.salt, 'hex').toString('base64'); var h = Buffer.from(account.hash, 'hex').toString(('base64')); var mqttPass = "PBKDF2$sha256$901$" + account.salt + "$" + account.hash; Account.update( {username: account.username}, {$set: {mqttPass: mqttPass, topics: topics._id}}, { multi: false }, function(err, count){ if (err) { logger.info("error adding MQTT account\n", err); console.log(err); } } ); } }); }); } else { console.log("MQTT account already exists"); } }); var app_id = 'http://localhost:' + port; if (process.env.VCAP_APPLICATION) { var application = JSON.parse(process.env.VCAP_APPLICATION); var app_uri = application['application_uris'][0]; app_id = 'https://' + app_uri; } var cookieSecret = 'ihytsrf334'; var logDirectory = path.join(__dirname, 'log'); fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory); var accessLogStream = rfs('access.log', { interval: '1d', // rotate daily compress: 'gzip', // compress rotated files maxFiles: 30, path: logDirectory }); var app = express(); app.set('view engine', 'ejs'); app.enable('trust proxy'); app.use(morgan("combined", {stream: accessLogStream})); app.use(cookieParser(cookieSecret)); app.use(flash()); app.use(session({ // genid: function(req) { // return genuuid() // use UUIDs for session IDs // }, secret: cookieSecret, resave: false, saveUninitialized: false, cookie: { //secure: true } })); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(passport.initialize()); app.use(passport.session()); function requireHTTPS(req, res, next) { if (req.get('X-Forwarded-Proto') === 'http') { //FYI this should work for local development as well var url = 'https://' + req.get('host'); if (req.get('host') === 'localhost') { url += ':' + port; } url += req.url; return res.redirect(url); } next(); } app.use(requireHTTPS); app.use('/',express.static('static')); passport.use(new LocalStrategy(Account.authenticate())); passport.use(new BasicStrategy(Account.authenticate())); passport.serializeUser(Account.serializeUser()); passport.deserializeUser(Account.deserializeUser()); var accessTokenStrategy = new PassportOAuthBearer(function(token, done) { oauthModels.AccessToken.findOne({ token: token }).populate('user').populate('grant').exec(function(error, token) { if (!error && token) { logger.info("db token: ", token.active); logger.info("db token.grant : ", token.grant.active); logger.info("db token.user: ", token.user); } if (!error && token && token.active && token.grant && token.grant.active && token.user) { // console.log("Token is GOOD!"); done(null, token.user, { scope: token.scope }); } else if (!error) { // console.log("TOKEN PROBLEM"); done(null, false); } else { // console.log("TOKEN PROBLEM 2"); done(error); } }); }); passport.use(accessTokenStrategy); app.get('/', function(req,res){ res.render('pages/index', {user: req.user, home: true}); }); app.get('/docs', function(req,res){ res.render('pages/docs', {user: req.user, docs: true}); }); app.get('/about', function(req,res){ res.render('pages/about', {user: req.user, about: true}); }); app.get('/login', function(req,res){ res.render('pages/login',{user: req.user, message: req.flash('error')}); }); app.get('/logout', function(req,res){ req.logout(); if (req.query.next) { //console.log(req.query.next); res.redirect(req.query.next); } else { res.redirect('/'); } }); //app.post('/login',passport.authenticate('local', { failureRedirect: '/login', successRedirect: '/2faCheck', failureFlash: true })); app.post('/login', passport.authenticate('local',{ failureRedirect: '/login', failureFlash: true, session: true }), function(req,res){ //console.log("login success"); //console.log(req.isAuthenticated()); //console.log(req.user); if (req.query.next) { res.reconnect(req.query.next); } else { //console.log("passed Auth"); res.redirect('/devices'); } }); function ensureAuthenticated(req,res,next) { //console.log("ensureAuthenticated - %j", req.isAuthenticated()); //console.log("ensureAuthenticated - %j", req.user); //console.log("ensureAuthenticated - %j", req.session); if (req.isAuthenticated()) { return next(); } else { //console.log("failed auth?"); res.redirect('/login'); } } app.get('/newuser', function(req,res){ res.render('pages/register',{user: req.user}); }); app.post('/newuser', function(req,res){ Account.register(new Account({ username : req.body.username, email: req.body.email, mqttPass: "foo" }), req.body.password, function(err, account) { if (err) { logger.info("new user problem\n", err); console.log(err); return res.status(400).send(err.message); } var topics = new Topics({topics: [ 'command/' + account.username +'/#', 'presence/'+ account.username + '/#', 'response/' + account.username + '/#' ]}); topics.save(function(err){ if (!err){ var s = Buffer.from(account.salt, 'hex').toString('base64'); var h = Buffer.from(account.hash, 'hex').toString(('base64')); var mqttPass = "PBKDF2$sha256$901$" + account.salt + "$" + account.hash; Account.update( {username: account.username}, {$set: {mqttPass: mqttPass, topics: topics._id}}, { multi: false }, function(err, count){ if (err) { console.log(err); } } ); } }); passport.authenticate('local')(req, res, function () { logger.info("created new user ", req.body.username); console.log("created new user %s", req.body.username); measurement.send({ t:'event', ec:'System', ea: 'NewUser', uid: req.body.username }); res.status(201).send(); }); }); }); app.get('/changePassword/:key',function(req, res, next){ var uuid = req.params.key; LostPassword.findOne({uuid: uuid}).populate('user').exec(function(error, lostPassword){ if (!error && lostPassword) { req.login(lostPassword.user, function(err){ if (!err){ lostPassword.remove(); res.redirect('/changePassword'); } else { console.log(err); res.redirect('/'); } }) } else { res.redirect('/'); } }); }); app.get('/changePassword', ensureAuthenticated, function(req, res, next){ res.render('pages/changePassword', {user: req.user}); }); app.post('/changePassword', ensureAuthenticated, function(req, res, next){ Account.findOne({username: req.user.username}, function (err, user){ if (!err && user) { user.setPassword(req.body.password, function(e,u){ // var s = Buffer.from(account.salt, 'hex').toString('base64'); // var h = Buffer.from(account.hash, 'hex').toString(('base64')); var mqttPass = "PBKDF2$sha256$901$" + user.salt + "$" + user.hash; u.mqttPass = mqttPass; u.save(function(error){ if (!error) { //console.log("Chagned %s's password", u.username); res.status(200).send(); } else { logger.info("Error changing ", u.username, "'s password"); console.log("Error changing %s's password", u.username); console.log(error); res.status(400).send("Problem setting new password"); } }); }); } else { console.log(err); res.status(400).send("Problem setting new password"); } }); }); app.get('/lostPassword', function(req, res, next){ res.render('pages/lostPassword', { user: req.user}); }); var sendemail = require('./sendemail'); var mailer = new sendemail(); app.post('/lostPassword', function(req, res, next){ var email = req.body.email; Account.findOne({email: email}, function(error, user){ if (!error){ if (user){ var lostPassword = new LostPassword({user: user}); //console.log(lostPassword); lostPassword.save(function(err){ if (!err) { res.status(200).send(); } //console.log(lostPassword.uuid); //console.log(lostPassword.user.username); var body = mailer.buildLostPasswordBody(lostPassword.uuid, lostPassword.user.username); mailer.send(email, 'alexa-node-red@hardill.me.uk', 'Password Reset for Alexa Node-RED', body.text, body.html); }); } else { res.status(404).send("No user found with that email address"); } } }); }); app.get('/auth/start',oauthServer.authorize(function(applicationID, redirectURI, done) { oauthModels.Application.findOne({ oauth_id: applicationID }, function(error, application) { if (application) { var match = false, uri = url.parse(redirectURI || ''); for (var i = 0; i < application.domains.length; i++) { if (uri.host == application.domains[i] || (uri.protocol == application.domains[i] && uri.protocol != 'http' && uri.protocol != 'https')) { match = true; break; } } if (match && redirectURI && redirectURI.length > 0) { done(null, application, redirectURI); } else { done(new Error("You must supply a redirect_uri that is a domain or url scheme owned by your app."), false); } } else if (!error) { done(new Error("There is no app with the client_id you supplied."), false); } else { done(error); } }); }),function(req,res){ var scopeMap = { // ... display strings for all scope variables ... access_devices: 'access you devices', create_devices: 'create new devices' }; res.render('pages/oauth', { transaction_id: req.oauth2.transactionID, currentURL: encodeURIComponent(req.originalUrl), response_type: req.query.response_type, errors: req.flash('error'), scope: req.oauth2.req.scope, application: req.oauth2.client, user: req.user, map: scopeMap }); }); app.post('/auth/finish',function(req,res,next) { //console.log("/auth/finish user: ", req.user); //console.log(req.body); //console.log(req.params); if (req.user) { next(); } else { passport.authenticate('local', { session: false }, function(error,user,info){ //console.log("/auth/finish authenting"); if (user) { //console.log(user.username); req.user = user; next(); } else if (!error){ //console.log("not authed"); req.flash('error', 'Your email or password was incorrect. Please try again.'); res.redirect(req.body['auth_url']) } })(req,res,next); } }, oauthServer.decision(function(req,done){ //console.log("decision user: ", req); done(null, { scope: req.oauth2.req.scope }); })); app.post('/auth/exchange',function(req,res,next){ var appID = req.body['client_id']; var appSecret = req.body['client_secret']; oauthModels.Application.findOne({ oauth_id: appID, oauth_secret: appSecret }, function(error, application) { if (application) { req.appl = application; next(); } else if (!error) { error = new Error("There was no application with the Application ID and Secret you provided."); next(error); } else { next(error); } }); }, oauthServer.token(), oauthServer.errorHandler()); app.get('/api/v1/devices', passport.authenticate(['bearer', 'basic'], { session: false }), function(req,res,next){ //console.log("all good, doing discover devices"); measurement.send({ t:'event', ec:'discover', ea: req.body.header ? req.body.header.name : "Node-RED", uid: req.user.username }); var user = req.user.username Devices.find({username: user},function(error, data){ if (!error) { var devs = []; for (var i=0; i< data.length; i++) { var dev = {}; dev.friendlyName = data[i].friendlyName; dev.friendlyDescription = data[i].friendlyDescription; dev.applianceId = "" + data[i].applianceId; dev.isReachable = data[i].isReachable; dev.actions = data[i].actions; dev.applianceTypes = data[i].applianceTypes; dev.additionalApplianceDetails = data[i].additionalApplianceDetails; dev.modelName = "Node-RED Endpoint"; dev.version = "0.0.1"; dev.manufacturerName = "Node-RED" devs.push(dev); } logger.info(user , " discovering " , JSON.stringify(devs) ); //console.log(devs) res.send(devs); } }); } ); var onGoingCommands = {}; mqttClient.on('message',function(topic,message){ if (topic.startsWith('response/')){ var payload = JSON.parse(message.toString()); var waiting = onGoingCommands[payload.messageId]; if (waiting) { //console.log("mqtt response: " + JSON.stringify(payload,null," ")); if (payload.success) { waiting.res.status(200); if (payload.extra) { if (typeof payload.extra == "object") { logger.info("extra payload", payload.extra); waiting.res.send(payload.extra); } else { logger.info("extra not an object: ", payload); logger.info(waiting.user); waiting.res.send({}); } } else { //console.log("not sending extra"); waiting.res.send({}); } } else { if (payload.extra && payload.extra.min) { //console.log("out of range"); waiting.res.status(416).send(payload.extra); } else { //console.log("malfunction"); waiting.res.status(503).send(); } } delete onGoingCommands[payload.messageId]; // should really parse uid out of topic measurement.send({ t:'event', ec:'command', ea: 'complete', uid: waiting.user }); } } }); var timeout = setInterval(function(){ var now = Date.now(); var keys = Object.keys(onGoingCommands); for (key in keys){ var waiting = onGoingCommands[keys[key]]; //console.log(keys[key]); if (waiting) { var diff = now - waiting.timestamp; if (diff > 6000) { //console.log("timed out"); waiting.res.status(504).send('{"error": "timeout"}'); delete onGoingCommands[keys[key]]; measurement.send({ t:'event', ec:'command', ea: 'timeout', uid: waiting.user }); } } } },500); app.post('/api/v1/command', passport.authenticate('bearer', { session: false }), function(req,res,next){ //console.log(req.user.username); //console.log(req.body); measurement.send({ e:'event', ec:'command', ea: req.body.header.name, uid: req.user.username }); var topic = "command/" +req.user.username + "/" + req.body.payload.appliance.applianceId; delete req.body.payload.accessToken; var message = JSON.stringify(req.body); try{ mqttClient.publish(topic,message); } catch (err) { } var command = { user: req.user.username, res: res, timestamp: Date.now() }; onGoingCommands[req.body.header.messageId] = command; } ); app.get('/devices', ensureAuthenticated, function(req,res){ var user = req.user.username; Devices.find({username:user}, function(err, data){ if (!err) { //console.log(data); res.render('pages/devices',{user: req.user ,devices: data, devs: true}); } }); }); app.put('/devices', ensureAuthenticated, function(req,res){ var user = req.user.username; var device = req.body; device.username = user; device.isReachable = true; var dev = new Devices(device); dev.save(function(err, dev){ if (!err) { res.status(201) res.send(dev); } else { res.status(500); res.send(err); } }); }); app.post('/device/:dev_id', ensureAuthenticated, function(req,res){ var user = req.user.username; var id = req.params.dev_id; var device = req.body; if (user === device.username) { Devices.findOne({_id: device._id, username: device.username}, function(err, data){ if (err) { res.status(500); res.send(err); } else if (data) { data.friendlyDescription = device.friendlyDescription; data.actions = device.actions; data.applianceTypes = device.applianceTypes; data.save(function(err, d){ res.status(201); res.send(d); }); } else { res.status(500); res.send("No such device"); logger.info("No such device"); logger.info("username: ", user); logger.info("device_id: ", id); } }); } }); app.delete('/device/:dev_id', ensureAuthenticated, function(req,res){ var user = req.user.username; var id = req.params.dev_id; console.log(id); Devices.remove({_id: id, username: user}, function(err) { if (err) { console.log(err); res.status(500); res.send(err); } else { res.status(202); res.send(); } }); }); app.post('/api/v1/devices', passport.authenticate('bearer', { session: false }), function(req,res,next){ var devices = req.body; if (typeof devices == 'object' && Array.isArray(devices)) { for (var i=0; i %s", app_id); setTimeout(function(){ },5000); });