Implementing ACL with webhooks for VerneMQ - caching

I am trying to get ACL behavior by implementing my own webhooks for VerneMQ. I am using express and apicache node packages for this. I hope the code is meaningful to non-javascript-programmers as well.
In my vernemq.conf I have set up my hooks, and they get called correctly:
$ vmq-admin webhooks show
+-----------------+------------------------------+-------------+
| hook | endpoint |base64payload|
+-----------------+------------------------------+-------------+
|auth_on_subscribe|http://127.0.0.1:3000/vmq/sub | true |
|auth_on_register |http://127.0.0.1:3000/vmq/auth| true |
| auth_on_publish |http://127.0.0.1:3000/vmq/pub | true |
+-----------------+------------------------------+-------------+
Also I disabled all other plugins and disabled anonymous login.
My webhooks implementation in express (simplified):
const express = require('express');
const apicache = require('apicache');
const bodyparser = require('body-parser');
// short cache times for demonstration
const authcache = apicache.middleware('15 seconds');
const pubcache = apicache.middleware('5 seconds');
const subcache = apicache.middleware('10 seconds');
const app = express();
const jsonparser = bodyparser.json();
app.use((req, res, next) => {
console.log(`${req.connection.remoteAddress}:${req.connection.remotePort} ${req.method} ${req.path}`);
return next();
});
app.post('/vmq/auth', authcache, (req, res) => {
return res.status(200).json({result: 'ok'});
});
app.post('/vmq/pub', pubcache, jsonparser, (req, res) => {
// this gets ignored most of the time because of caching
if (req.body.topic === 'only/allowed/topic') {
return res.status(200).json({result: 'ok'});
}
return res.status(401).end();
});
app.post('/vmq/sub', subcache, (req, res) => {
return res.status(200).json({result: 'ok'});
});
app.use((req, res, next) => {
return res.status(404).end();
});
app.use((err, res, req, next) => {
console.error(err);
return res.status(500).end();
});
const server = app.listen(3000, 'localhost', () => {
const address = server.address();
return console.log(`listening on ${address.address}:${address.port} ...`);
});
Using mqtt.js I wrote a client (simplified):
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://localhost');
client.on('connect', () => {
setInterval(() => {
client.publish('only/allowed/topic', 'working');
client.publish('some/disallowed/topic', 'working too :(');
}, 500);
return client.subscribe('some/disallowed/topic');
});
client.on('message', (topic, message) => {
return console.log(`${topic}:${message}`);
});
What happens is that the client successfully authenticates and then publishes to only/allowed/topic, which is allowed and gets cached as successful by VerneMQ. However, since the successful call to /vmq/pub is now cached, publishing to some/disallowed/topic also works. If I change the order of publishing, both will fail.
I would have expected VerneMQ to map the cached results to all parameters in a call, except the payload of course, and not just to a client connection. However that is not the case. What's a possible way to implement ACL via webhooks while using caching? Not using caching is out of the question, as this kills my performance, and caching is recommended by the docs anways.
Also, will someone with 1500+ rep be so nice and create the tag vernemq? :)

I misunderstood how apicache works and what it actually does. All I needed to do was just setting the appropriate header for caching, as stated in the docs of VerneMQ. Apparently apicache stores the actual result and returns that whenever asked within the specified timeframe, no matter what the client is actually requesting.
This is the working code now:
const express = require('express');
const bodyparser = require('body-parser');
const app = express();
// short cache times for demonstration (in seconds)
const authcachetime = 15;
const pubcachetime = 5;
const subcachetime = 10;
const jsonparser = bodyparser.json();
app.use((req, res, next) => {
console.log(`${req.connection.remoteAddress}:${req.connection.remotePort} ${req.method} ${req.path}`);
return next();
});
app.post('/vmq/auth', (req, res) => {
res.set('cache-control', `max-age=${authcachetime}`);
return res.status(200).json({result: 'ok'});
});
app.post('/vmq/pub', jsonparser, (req, res) => {
res.set('cache-control', `max-age=${pubcachetime}`);
if (req.body.topic === 'only/allowed/topic') {
return res.status(200).json({result: 'ok'});
}
return res.status(401).end();
});
app.post('/vmq/sub', (req, res) => {
res.set('cache-control', `max-age=${subcachetime}`);
return res.status(200).json({result: 'ok'});
});
app.use((req, res, next) => {
return res.status(404).end();
});
app.use((err, res, req, next) => {
console.error(err);
return res.status(500).end();
});
const server = app.listen(3000, 'localhost', () => {
const address = server.address();
return console.log(`listening on ${address.address}:${address.port} ...`);
});
As expected the client now gets an error when it tries to publish to an illegal topic.

Related

Dialogflow v2 Actions on Google response timeout

Hi I have a timeout problem to get a json response; I am using google places API to look for the closest location.
Could anyone help me with this? Thanks.
const PlaceSearch = require("./node_modules/googleplaces/lib/NearBySearch.js");
const PlaceDetailsRequest = require("./node_modules/googleplaces/lib/PlaceDetailsRequest.js");
app.intent('Ask Location', conv => {conv.ask(new Permission({context: 'To start',permissions: 'DEVICE_PRECISE_LOCATION',}));});
app.intent('geolocation.intent', (conv,params,granted) =>{
if(granted){
var coordinates = conv.device.location.coordinates;
var location = [coordinates.latitude, coordinates.longitude];
var searchParameters = {
location: location,
name:'Store Name',
radius:10000
};
var config = {
apiKey:'#####',
outputFormat:'json'
};
var placeSearch = new PlaceSearch(config.apiKey, config.outputFormat);
var placeDetailsRequest = new PlaceDetailsRequest(config.apiKey, config.outputFormat);
placeSearch(searchParameters, function (error, search_response) {
if(search_response.status === 'OK'){
placeDetailsRequest({reference: search_response.results[0].reference}, function (error, details_response) {
conv.ask(`Your closest store is at ${details_response.result.formatted_address}.`);
});
}
});
}
});
I solved the issue using a request to Google API via URL; and using a promise.
const request = require("request");
app.input("geolocation.intent", conv => {
return new Promise((resolve, reject) => {
...
request(options, (error, response, body) => {
...
if (error) {
...
reject(...);
} else {
...
resolve(...);
}
}).then(result => {
const address = result.address;
conv.ask('Your closest store is...');
}).catch(error => {
conv.close('Error in Promise');
});
});
What I learned is that in Dialogflow API v2 you need to use promises when you make a request.

Cannot POST ajax request

can anyone explain to me why i cannot post the ajax request. When i run this code the console appear POST http://localhost:8080/api/users 404 (Not Found), and in the networth part the preview is 404 not found
In the index.js file
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev.js');
var app = express();
var compiler = webpack(config);
var bodyParser = require('body-parser');
var users = require('./server/routes/users');
app.use(bodyParser.json());
app.use('/api/users', users);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(8080, 'localhost', function(err) {
if (err) {
console.log(err);
return;
}
console.log('Listening at http://localhost:8080');
});
and the users file
var express = require('express')
var router = express.Router()
// middleware that is specific to this router
function validateInput(data) {
let errors = {};
if (Validator.isNull(data.username)){
errors.username = "This field is required";
}
if (Validator.isEmail(data.email)) {
errors.email = "Email is invalid";
}
if (Validator.isNull(data.password)){
errors.password = 'This field is required';
}
if (Validator.isNull(data.passwordConfirmation)){
errors.passwordConfirmation = 'This field is required';
}
if (Validator.equals(data.password, data.passwordConfirmation)){
errors.passwordConfirmation = 'Password must match';
}
return {
errors,
isValid: isEmpty(errors)
}
}
router.post('/api/users', (req, res) => {
console.log('runiing the router/post');
console.log(req.body);
const {errors, isValid} = validateInput(req.body);
if (!isValid) {
res.status(400).json(errors);
}
});
module.exports = router
in users.js file the function should be
router.post('/', (req, res) => {
console.log('runiing the router/post');
console.log(req.body);
const {errors, isValid} = validateInput(req.body);
if (!isValid) {
res.status(400).json(errors);
}
});
Because index.js have already resolved /api/users part in the request url http://localhost:8080/api/users at this point. So you only have to map after the /api/users in your users js file.
For example, if you have following function in users.js file
router.post('/:id', (req, res) => {
...
}
It will resole to the path http://localhost:8080/api/users/1
Edit
In your existing version,
router.post('/api/users', (req, res) => {
...
}
will resolve as http://localhost:8080/api/users/api/users

How to handle Google OAuth flow via redux-saga

I am trying to implement Google OAuth 2 with with redux saga.
I have a watcher in my saga listening for GOOGLE_AUTH action which then executes googleLogin
function *watchGoogleAuth() {
yield *takeLatest(GOOGLE_AUTH, googleLogin)
}
function *googleLogin() {
const id_token = yield call(GoogleSignIn);
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
The implementation for GoogleSignIn is in apis.js
export function GoogleSignIn() {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
return {
id_token: GoogleUser.getAuthResponse().id_token
};
},
(err) => {
console.log(err)
}
)
}
But saga doesn't seem to wait for the GoogleSignIn to complete. As soon as OAuth consent screen pops up, saga proceeds executing the console.log without waiting for google signin promise to return actual data.
Is there any better way to handle this situation? Thanks!
To expand on #HenrikR's answer, the generator will not wait unless it receives a promise.
export const GoogleSignIn = () => {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
return new Promise((resolve, reject) => {
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
resolve(GoogleUser.getAuthResponse().id_token);
},
(err) => {
reject(err);
}
);
});
};
Accordingly, you should wrap the yield statement in a try/catch. Simplified and somewhat lazy:
function *googleLogin() {
try {
const id_token = yield call(GoogleSignIn);
if (id_token) { /* Possibly with more checks and validations */
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
} catch (e) {
console.log(e);
}
}

Simple bluebird example with restify doesn't work

taking straight from this post:
This code never executes.
var Promise = require("bluebird");
Promise.promisifyAll(require("restify"));
var restify = require("restify");
var http = require('http');
const PORT=7070;
function handleRequest(request, response){
response.end('It Works!! Path Hit: ' + request.url);
}
var server = http.createServer(handleRequest);
server.listen(PORT, function(){
console.log("Server listening on: http://localhost:%s", PORT);
});
var client = restify.createJsonClientAsync({
url: 'http://127.0.0.1:7070'
});
client.get("/foo").spread(function(req, res, obj) {
console.log(obj);
});
I only put together this simple example to prove it to myself after my production code didn't work. I can hit localhost:7070 with curl and I get the expected results.
In a nutshell: I need to execute 3 GET calls to a server before I can create a POST and hence my need for promises.
Anyone can shed some insight? I can't imagine this being simpler.
UPDATE
Apparently i did not read the question correctly, here is a working example of 2 gets using a promisified restify json client. you would just do another spread in the body of the second spread for your post.
var promise = require('bluebird');
var restify = require('restify');
promise.promisifyAll(restify.JsonClient.prototype);
var client = restify.createJsonClient({
url: 'http://localhost:8080',
version: '*'
});
client.getAsync('/api/resource/1').spread(function(req, res, obj) {
console.log('result 1', obj);
return client.getAsync('/api/resource/2').spread(function(req, res, obj) {
console.log('result 2', obj);
});
});
As I stated in my comments, I would not promisify restify itself. Instead I would use either a handler whose body executes promise code or a chain of handlers (which can also have promises in the body). restify should only receive the request and execute the handler.
I will use modified versions of the basic example from the restify page to illustrate each.
Promise in the message body using knex.js which returns a promise
var knex = require('knex')(connectionConfig);
var restify = require('restify');
function promisePost(req, res, next) {
// get 1
knex.select('*')
.from('table1')
.where('id', '=', req.body.table1_id)
.then(function(result1) {
// get 2
return knex.select('*')
.from('table2')
.where('id', '=', req.body.table2_id)
.then(function(result2) {
return knex('table3').insert({
table1_value: result1.value,
table2_value: result2.value
})
.then(function(result3) {
res.send(result3);
return next();
});
});
});
}
var server = restify.createServer();
server.use(restify.bodyParser());
server.post('/myroute', promisePost);
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});
now with chained handlers
var knex = require('knex')(connectionConfig);
var restify = require('restify');
function get1(req, res, next) {
knex.select('*').from('table1')
.where('id', '=', req.body.table1_id)
.then(function(result1) {
res.locals.result1 = result1;
return next();
});
}
function get2(req, res, next) {
knex.select('*').from('table2')
.where('id', '=', req.body.table2_id)
.then(function(result2) {
res.locals.result2 = result2;
return next();
});
}
function post(req, res, next) {
knex('table3').insert({
table1_value: res.locals.result1,
table2_value: res.locals.result2
})
.then(function(result3) {
res.send(result3);
return next();
});
}
var server = restify.createServer();
server.use(restify.bodyParser());
server.post('/myroute', get1, get2, post);
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});

How to make middleware for response on all ajax requests

I need to make a single middleware that will handle each response to web user. I try to make something like the following:
function ajaxResponseMiddleware(req, res, next) {
var code = res.locals._code || 200;
var data = res.locals._response;
res.json(code, data);
}
app.get('/ajax1', function(req, res, next){
// Do something and add data to be responsed
res.locals._response = {test: "data2"};
// Go to the next middleware
next();
}, ajaxResponseMiddleware);
app.get('/ajax2', function(req, res, next){
// Do something and add data to be responsed
res.locals._response = {test: "data2"};
res.locals._code = 200;
// Go to the next middleware
next();
}, ajaxResponseMiddleware);
The response is handled in ajaxResponseMiddleware function where I can add some default state for all my ajax responses.
One thing that I don't like in the approach above is the adding ajaxResponseMiddleware function in each route.
So what do you think about this approach? May you advise improvements or share your experience.
middleware is just a function function (req, res, next) {}
var express = require('express');
var app = express();
// this is the middleware, you can separate to new js file if you want
function jsonMiddleware(req, res, next) {
res.json_v2 = function (code, data) {
if(!data) {
data = code;
code = 200;
}
// place your modification code here
//
//
res.json(code, data)
}
next();
}
app.use(jsonMiddleware); // Note: this should above app.use(app.router)
app.use(app.router);
app.get('/ajax1', function (req, res) {
res.json_v2({
name: 'ajax1'
})
});
app.listen(3000);

Resources