Skip to content

Instantly share code, notes, and snippets.

@krlicmuhamed
Last active August 12, 2016 22:56
Show Gist options
  • Save krlicmuhamed/3b1f92d5e916ee11fb795bc883a1a5aa to your computer and use it in GitHub Desktop.
Save krlicmuhamed/3b1f92d5e916ee11fb795bc883a1a5aa to your computer and use it in GitHub Desktop.
Simple API KEY security using ActionHero and Redis
'use strict';
var uuid = require('node-uuid');
exports.action = {
name: 'securityApiKey', // Internal security action. Do not change the name.
description: 'I hand out api keys.',
version: 1.0,
inputs: {
uniqueId: {
required: true,
validator: function(param, connection, actionTemplate){
if(typeof param !== 'string'){ return 'uniqueId must be a string'; }else{ return true; }
}
}
},
run: function(api, data, next) {
var apikey = 'test-'+uuid();
var hash = 'api:key:'+apikey;
api.redis.clients.client.hmset(hash, 'uniq', data.params.uniqueId, 'activated', 0, function(err, result){
if(!err){
data.response.apikey = apikey;
return next();
}else{
api.log(err,'error');
data.connection.rawConnection.responseHttpCode = 500;
return next({
code: data.connection.rawConnection.responseHttpCode,
message: 'Couldn\'t generate a key.'
});
}
});
}
};
'use strict';
if (process.env.NODE_ENV == "test") {
exports.action = {
name: 'securityTest',
description: 'Only used in test/development envirovments.',
blockedConnectionTypes: [],
protected: true,
inputs: {},
run: function(api, data, next) {
data.response.message = 'This action is protected.';
return next();
}
};
}
exports['default'] = {
security: function(api) {
return {
enabled: {
web: true,
websocket: false,
socket: false,
mqtt: false
}
};
}
};
'use strict';
module.exports = {
loadPriority: 1001,
startPriority: 1001,
stopPriority: 1001,
initialize: function(api, next) {
api.actions.addMiddleware({
name: 'API Security middleware',
global: true,
priority: 100,
preProcessor: function(data, next) {
data.connection.security = {
authorized: false
};
// Check If enabled
if (!api.config.security.enabled[data.connection.type]) {
return next();
} else if (data.actionTemplate.protected) {
if (data.actionTemplate.protected && data.actionTemplate.name === 'securityApiKey') {
data.connection.rawConnection.responseHttpCode = 501;
return next({
code: data.connection.rawConnection.responseHttpCode,
message: 'Don\'t define protected template in securityApiKey action!'
});
}
// What apikey and role this connection uses?
var request = {
// Other servers like socket can use data.connection.mockHeaders
headers: data.params.httpHeaders || (data.connection.rawConnection.req ? data.connection.rawConnection.req.headers : undefined) || data.connection.mockHeaders || {},
uri: data.connection.rawConnection.req ? data.connection.rawConnection.req.uri : {}
};
var authHeader = request.headers.authorization ||
request.headers.Authorization ||
false;
if (authHeader) {
// Authenticate the requester
api.redis.clients.client.hgetall('api:key:'+authHeader, function(err, result){
if(!err){
data.connection.security.authorized = true;
if(result.activated === '0'){
data.connection.rawConnection.responseHttpCode = 401;
return next({
code: data.connection.rawConnection.responseHttpCode,
message: 'Specified api key is not activated yet.'
});
}
return next();
}else{
data.connection.rawConnection.responseHttpCode = 500;
return next({
code: data.connection.rawConnection.responseHttpCode,
message: 'Failed fetching the api key.'
});
}
});
} else {
data.connection.rawConnection.responseHttpCode = 401;
return next({
code: data.connection.rawConnection.responseHttpCode,
message: 'Authorization header is missing.'
});
}
} else {
// skip this action.
return next();
}
},
postProcessor: function(data, next) {
if (api.config.security.enabled[data.connection.type] &&
data.connection.security.authorized) {
//TODO: api key statistics
}
return next();
}
});
api.log('Security initialized.');
return next();
},
start: function(api, next) {
return next();
},
stop: function(api, next) {
return next();
}
};
process.env.NODE_ENV = 'test';
var request = require('request');
var should = require('should');
var actionheroPrototype = require('actionhero').actionheroPrototype;
var actionhero = new actionheroPrototype();
var api, url, apikey;
describe('security', function() {
before(function(done) {
actionhero.start(function(error, a) {
api = a;
url = 'http://' + api.config.servers.web.bindIP + ':' + api.config.servers.web.port;
done();
})
});
after(function(done) {
actionhero.stop(function(error) {
done();
});
})
it('securityApiKey', function(done) {
api.specHelper.runAction('securityApiKey', {
uniqueId: 'test123kasjdjhud'
}, function(response) {
//console.log(response);
should.exist(response.apikey);
apikey = response.apikey;
should.not.exist(response.error);
done();
});
});
it('securityTest should return error if header is missing', function(done) {
var options = {
method: 'get',
url: url + '/api/securityTest',
timeout: 1500
};
request(options, function(error, response, body) {
if (!error) {
body = JSON.parse(body);
should.exist(body.error);
response.statusCode.should.equal(401);
body.error.message.should.equal('Authorization header is missing.');
done();
} else {
throw error;
}
});
});
it('securityTest should error unactive', function(done) {
var options = {
method: 'get',
url: url + '/api/securityTest',
timeout: 1500,
headers: {
Authorization: apikey
}
};
request(options, function(error, response, body) {
if (!error) {
body = JSON.parse(body);
should.exist(body.error);
response.statusCode.should.equal(401);
body.error.message.should.equal('Specified api key is not activated yet.');
done();
} else {
throw error;
}
});
});
it('securityTest should authorize', function(done) {
// This HSET command should be called from a secure network or in some other
// secure process, because this is how you activate an api key.
api.redis.clients.client.hset('api:key:'+apikey, 'activated', '1', function(err, result){
should.not.exist(err);
var options = {
method: 'get',
url: url + '/api/securityTest',
timeout: 1500,
headers: {
Authorization: apikey
}
};
request(options, function(error, response, body) {
if (!error) {
body = JSON.parse(body);
should.not.exist(body.error);
response.statusCode.should.equal(200);
body.message.should.equal('This action is protected.');
done();
} else {
throw error;
}
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment