Big Update

This adds:
 - google analytics to the command handling
 - the start of support for the new temperature queries
 - fixes the oauth token refresh code
 - reuses still active oauth tokens
This commit is contained in:
Ben Hardill
2017-02-27 15:00:22 +00:00
parent c78002c1d4
commit a7724eca2a
13 changed files with 241 additions and 36 deletions

48
googleMeasurement.js Normal file
View File

@@ -0,0 +1,48 @@
var request = require('request');
var querystring = require('querystring');
var baseURL = "https://www.google-analytics.com/";
var Measurements = function(tid) {
this.tid = tid;
};
Measurements.prototype.send = function(options) {
if (!this.tid){
return;
}
var required = {
v: 1,
tid: this.tid,
t: 'event'
};
var url = baseURL;
var body;
if (Array.isArray(options)) {
for (var i=0; i<options.length && i < 20; i++) {
var temp = Object.assign(required, options[i]);
body += querystring.stringify(temp);
body += "\n";
}
url += "batch";
} else {
options = Object.assign(required, options);
body = querystring.stringify(options);
url += "collect";
}
// console.log(body);
request.post({url: url, body: body},
function(err, response, body){
if (err) {
console.log(err);
}
// console.log("Analytics response: %d",response.statusCode);
// console.log(response.headers);
// console.log(body);
});
}
module.exports = Measurements;

View File

@@ -10,6 +10,7 @@ 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;
@@ -26,6 +27,9 @@ 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 = {
@@ -68,6 +72,7 @@ if (process.env.VCAP_SERVICES) {
}
console.log(mongo_url);
mongoose.Promise = global.Promise;
var mongoose_options = {
server: {
auto_reconnect:true,
@@ -246,14 +251,24 @@ app.get('/login', function(req,res){
app.get('/logout', function(req,res){
req.logout();
res.redirect('/');
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 }),
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true, session: true }),
function(req,res){
res.redirect('/devices');
if (req.query.next) {
res.reconnect(req.query.next);
} else {
res.redirect('/devices');
}
});
function ensureAuthenticated(req,res,next) {
@@ -303,6 +318,12 @@ app.post('/newuser', function(req,res){
passport.authenticate('local')(req, res, function () {
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();
});
@@ -340,7 +361,7 @@ app.get('/auth/start',oauthServer.authorize(function(applicationID, redirectURI,
res.render('pages/oauth', {
transaction_id: req.oauth2.transactionID,
currentURL: req.originalUrl,
currentURL: encodeURIComponent(req.originalUrl),
response_type: req.query.response_type,
errors: req.flash('error'),
scope: req.oauth2.req.scope,
@@ -365,7 +386,7 @@ app.post('/auth/finish',function(req,res,next) {
next();
} else if (!error){
//console.log("not authed");
req.flash('error', 'Your email or password was incorrect. Try again.');
req.flash('error', 'Your email or password was incorrect. Please try again.');
res.redirect(req.body['auth_url'])
}
})(req,res,next);
@@ -397,6 +418,12 @@ app.get('/api/v1/devices',
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){
@@ -450,6 +477,13 @@ mqttClient.on('message',function(topic,message){
}
}
delete onGoingCommands[payload.messageId];
// should really parse uid out of topic
measurement.send({
t:'event',
ec:'command',
ea: 'complete',
uid: waiting.user
});
}
}
});
@@ -466,6 +500,12 @@ var timeout = setInterval(function(){
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
});
}
}
}
@@ -476,6 +516,12 @@ app.post('/api/v1/command',
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);
@@ -485,6 +531,7 @@ app.post('/api/v1/command',
}
var command = {
user: req.user.username,
res: res,
timestamp: Date.now()
};

View File

@@ -35,7 +35,7 @@ var AccessTokenSchema = new Schema({
scope: [ { type: String }],
expires: { type: Date, default: function(){
var today = new Date();
var length = 60 * 24 * 365 * 5; // Length (in minutes) of our access token
var length = 60 * 24 * 90; // Length (in minutes) of our access token
return new Date(today.getTime() + length*60000);
} },
active: { type: Boolean, get: function(value) {

137
oauth.js
View File

@@ -7,15 +7,31 @@ server.grant(oauth2orize.grant.code({
scopeSeparator: [ ' ', ',' ]
}, function(application, redirectURI, user, ares, done) {
//console.log("grant user: ", user);
OAuth.GrantCode.findOne({application: application, user: user},function(error,grant){
if (!error && grant) {
done(null,grant.code);
} else if (!error) {
var grant = new OAuth.GrantCode({
application: application,
user: user,
scope: ares.scope
});
grant.save(function(error) {
done(error, error ? null : grant.code);
});
} else {
done(error,null);
}
});
var grant = new OAuth.GrantCode({
application: application,
user: user,
scope: ares.scope
});
grant.save(function(error) {
done(error, error ? null : grant.code);
});
// var grant = new OAuth.GrantCode({
// application: application,
// user: user,
// scope: ares.scope
// });
// grant.save(function(error) {
// done(error, error ? null : grant.code);
// });
}));
server.exchange(oauth2orize.exchange.code({
@@ -23,31 +39,106 @@ server.exchange(oauth2orize.exchange.code({
}, function(application, code, redirectURI, done) {
OAuth.GrantCode.findOne({ code: code }, function(error, grant) {
if (grant && grant.active && grant.application == application.id) {
OAuth.AccessToken.findOne({application:application, user: grant.user, active: true}, function(error,token){
if (token) {
OAuth.RefreshToken.findOne({application:application, user: grant.user},function(error, refreshToken){
if (refreshToken){
done(null,token.token, refreshToken.token,{token_type: 'standard'});
} else {
// Shouldn't get here unless there is an error as there
// should be a refresh token if there is an access token
done(error);
}
});
} else if (!error) {
var token = new OAuth.AccessToken({
application: grant.application,
user: grant.user,
grant: grant,
scope: grant.scope
});
token.save(function(error){
//delete old refreshToken or reuse?
OAuth.RefreshToken.findOne({application:application, user: grant.user},function(error, refreshToken){
if (refreshToken) {
done(error, error ? null : token.token, refreshToken.token, error ? null : { token_type: 'standard' });
} else if (!error) {
var refreshToken = new OAuth.RefreshToken({
user: grant.user,
application: grant.application
});
refreshToken.save(function(error){
done(error, error ? null : token.token, refreshToken.token, error ? null : { token_type: 'standard' });
});
} else {
done(error);
}
});
});
} else {
done(error);
}
});
//console.log("exchange user ", grant.user);
var token = new OAuth.AccessToken({
application: grant.application,
user: grant.user,
grant: grant,
scope: grant.scope
});
// var token = new OAuth.AccessToken({
// application: grant.application,
// user: grant.user,
// grant: grant,
// scope: grant.scope
// });
token.save(function(error) {
// token.save(function(error) {
var refreshToken = new OAuth.RefreshToken({
user: grant.user,
application: grant.application
});
// var refreshToken = new OAuth.RefreshToken({
// user: grant.user,
// application: grant.application
// });
refreshToken.save(function(error){
done(error, error ? null : token.token, refreshToken.token, error ? null : { token_type: 'standard' });
});
});
// refreshToken.save(function(error){
// done(error, error ? null : token.token, refreshToken.token, error ? null : { token_type: 'standard' });
// });
// });
} else {
done(error, false);
}
});
}));
server.exchange(oauth2orize.exchange.refreshToken({
userProperty: 'appl'
}, function(application, token, scope, done){
console.log("Yay!");
OAuth.RefreshToken.findOne({token: token}, function(error, refresh){
if (refresh && refresh.application == application.id) {
OAuth.GrantCode.findOne({},function(error, grant){
if (grant && grant.active && grant.application == application.id){
var newToken = new OAuth.AccessToken({
application: refresh.application,
user: refresh.user,
grant: grant,
scope: scope
});
newToken.save(function(error){
if (!error) {
done(null, newToken.token);
} else {
done(error,false);
}
});
} else {
done(error,null);
}
});
} else {
done(error, false);
}
});
}));
server.serializeClient(function(application, done) {
done(null, application.id);

View File

@@ -32,6 +32,8 @@
"passport-local": "^1.0.0",
"passport-local-mongoose": "^4.0.0",
"passport-totp": "0.0.2",
"querystring": "^0.2.0",
"request": "^2.79.0",
"uid2": "0.0.3"
},
"devDependencies": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 641 B

View File

@@ -14,8 +14,9 @@ app.use(session({
// return genuuid() // use UUIDs for session IDs
// },
secret: 'boo',
resave: false,
resave: true,
saveUninitialized: false,
name: 'bar',
cookie: {
secure: true
}
@@ -30,7 +31,7 @@ passport.use(new OAuth2Strategy({
// tokenURL: 'https://alexa-node-red.eu-gb.mybluemix.net/auth/exchange',
authorizationURL: 'https://localhost:3000/auth/start',
tokenURL: 'https://localhost:3000/auth/exchange',
clientID: '1',
clientID: '2',
clientSecret: 'password1234',
scope: "access_devices",
callbackURL: 'http://localhost:3001/callback'

View File

@@ -37,7 +37,6 @@
<input type="checkbox" name="actions" id="turnOn" value="turnOn">
<label for="turnOff">Off: </label>
<input type="checkbox" name="actions" id="turnOff" value="turnOff">
<br>
<fieldset id="percentCheck">
<label for="setPercentage">%: </label>
<input type="checkbox" name="actions" id="setPercentage" value="setPercentage" onclick='checkCapability(this)'>
@@ -46,7 +45,6 @@
<label for="decrementPercentage">-%: </label>
<input type="checkbox" name="actions" id="decrementPercentage" value="decrementPercentage" onclick='checkCapability(this)'>
</fieldset>
<br>
<fieldset id="temperatureCheck">
<label for="setTargetTemperature">&deg;C/F: </label>
<input type="checkbox" name="actions" id="setTargetTemperature" value="setTargetTemperature" onclick='checkCapability(this)'>
@@ -55,6 +53,14 @@
<label for="decrementTargetTemperature">-&deg;C/F: </label>
<input type="checkbox" name="actions" id="decrementTargetTemperature" value="decrementTargetTemperature" onclick='checkCapability(this)'>
</fieldset>
<!--
<fieldset id="queryTemperature">
<label ofr="getTargetTemperature">Query Set Point: </label>
<input type="checkbox" name="actions" id="getTargetTemperature" value="getTargetTemperature" onclick='checkCapability(this)'>
<label ofr="getTemperatureReading">Query Current Temp: </label>
<input type="checkbox" name="actions" id="getTemperatureReading" value="getTemperatureReading" onclick='checkCapability(this)'>
</fieldset>
-->
</fieldset>
</div>
@@ -197,8 +203,10 @@
var temp = $('#setTargetTemperature').prop('checked');
var incTemp = $('#incrementTargetTemperature').prop('checked');
var decTemp = $('#decrementTargetTemperature').prop('checked');
var qSetTemp = $('#getTargetTemperature').prop('checked');
var qCurTemp = $('#getTemperatureReading').prop('checked');
var t = temp | incTemp | decTemp;
var t = temp | incTemp | decTemp | qSetTemp | qCurTemp;
if (p & t) {
alert("You can not control both percentage and temperature on the same device");

View File

@@ -5,7 +5,7 @@
<input type="hidden" name="transaction_id" value="<%= transaction_id %>">
<input type="hidden" name="response_type" value="<%= response_type %>">
<input type="hidden" name="client_id" value="<%= application.oauth_id %>">
<input type="hidden" name="auth_url" value="<%= currentURL %>">
<input type="hidden" name="auth_url" value="<%= decodeURIComponent(currentURL) %>">
<input type="hidden" name="scope" value="<%= scope.join(',') %>">
<div class="">
@@ -26,10 +26,13 @@
<% if (user) { %>
<div class="form-group">
<p>Signed in as <strong><%= user.name%></strong>.</p>
<a href="/logout?next=">Not </a><%= user.username %>?
<a href="/logout?next=<%= currentURL %>">Not </a><%= user.username %>?
<input type="submit" value="Authorise">
</div>
<% } else { %>
<% if (errors) { %>
<p style="color: red"><%= errors %></p>
<% } %>
<div class="form-group">
<label for="username">Username: </label>
<input type="text" id="username" name="username"/>

View File

@@ -24,6 +24,11 @@
var password = document.getElementById('password').value;
var passwordAgain = document.getElementById('passwordAgain').value;
if (username.length < 1) {
alert("Please enter a valid username");
return;
}
if (password !== passwordAgain) {
alert("Passwords don't match");
return;