Skip to content

Commit

Permalink
Intermediary step to demonstrate how to integrate authorisation into …
Browse files Browse the repository at this point in the history
…the API
  • Loading branch information
vgheri committed Nov 3, 2013
1 parent 46c09bc commit b60114d
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 40 deletions.
20 changes: 10 additions & 10 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ module.exports = function(grunt) {
reporter: 'spec'
}
},
testSingleModule: {
src: 'test/ApiAccessToken.js',
options: {
ui: 'bdd',
require: [
'should'
],
reporter: 'spec'
}
}
testSingleModule: {
src: 'test/Routes.js',
options: {
ui: 'bdd',
require: [
'should'
],
reporter: 'spec'
}
}
}
});

Expand Down
4 changes: 2 additions & 2 deletions Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
* Time: 18.11
* To change this template use File | Settings | File Templates.
*/
function setup(app, handlers) {
function setup(app, handlers, authorise) {
app.post('/api/profiles', handlers.account.createAccount);
app.get('/api/profiles/:username', handlers.account.getAccount);
app.put('/api/profiles/:username', handlers.account.updateAccount);
app.del('/api/profiles/:username', handlers.account.deleteAccount);
app.post('/api/profiles/:userId/lists', handlers.list.createShoppingList);
app.post('/api/profiles/:userId/lists/:templateId', handlers.list.createShoppingList);
app.put('/api/profiles/:userId/lists/:shoppingListId', handlers.list.updateShoppingList);
app.get('/api/profiles/:userId/lists/:shoppingListId', handlers.list.getShoppingList);
app.get('/api/profiles/:userId/lists/:shoppingListId', authorise, handlers.list.getShoppingList);
app.get('/api/profiles/:userId/lists', handlers.list.getShoppingLists);
app.del('/api/profiles/:userId/lists/:shoppingListId', handlers.list.deleteShoppingList);
app.post('/api/profiles/:userId/lists/:shoppingListId/item/', handlers.list.addShoppingItem);
Expand Down
4 changes: 3 additions & 1 deletion Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var ShoppingListHandler = require('./handlers/ShoppingListHandler');
var AuthenticationHandler = require('./handlers/AuthenticationHandler');
var routes = require('./Routes');
var fs = require('fs');
var securityPolicy = require('./securityPolicy');

var expressLogFile = fs.createWriteStream('./logs/express.log', {flags: 'a'});
//var viewEngine = 'jade'; // modify for your view engine
Expand All @@ -37,14 +38,15 @@ app.configure('production', function(){
app.use(express.errorHandler());
});


var handlers = {
account: new AccountHandler(),
list: new ShoppingListHandler(),
auth: new AuthenticationHandler()
};

function start() {
routes.setup(app, handlers);
routes.setup(app, handlers, securityPolicy.authorise);
var port = process.env.PORT || 3000;
app.listen(port);
console.log("Express server listening on port %d in %s mode", port, app.settings.env);
Expand Down
7 changes: 6 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
- Modify test/Routes.js to add a after() method to cleanup db and close connection mongodb

- Account Search and delete: GET, DELETE -> /profiles/:username should only work
for the current user , otherwise return 403 FORBIDDEN
for the current user , otherwise return 401 NOT AUTHORIZED

@ Refactor securityPolicy.js into a new handler called AuthorisationHandler.
@ Refactor Server.js where I inject the authroise method
@ Refactor Routes.js test file to always send the api access token and use the api access token


- Secure each API endpoint so that we check for the API ACCESS TOKEN that is valid, and that the user id related to the
token is the same user id contained in the URL. Otherwise return 403 FORBIDDEN
28 changes: 14 additions & 14 deletions infrastructure/securityToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,20 @@ securityTokenSchema.statics.removeSecurityToken = function(apiAccessToken) {
};

securityTokenSchema.statics.authorise = function(apiAccessToken, userId) {
var deferred = Q.defer();
SecurityToken.findSecurityToken(apiAccessToken)
.then(function(securityToken) {
if (securityToken !== null && securityToken.userId.toString() === userId.toString()) {
deferred.resolve(true);
}
else {
deferred.resolve(false);
}
})
.fail(function(err) {
deferred.reject(err);
});
return deferred.promise;
var deferred = Q.defer();
SecurityToken.findSecurityToken(apiAccessToken)
.then(function(securityToken) {
if (securityToken !== null && securityToken.userId.toString() === userId.toString()) {
deferred.resolve(true);
}
else {
deferred.resolve(false);
}
})
.fail(function(err) {
deferred.reject(err);
});
return deferred.promise;
};

var SecurityToken = mongoose.model('SecurityToken', securityTokenSchema);
Expand Down
35 changes: 35 additions & 0 deletions securityPolicy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* To change this template use File | Settings | File Templates.
*/

var SecurityToken = require('./infrastructure/securityToken');
var logger = require('utils/logger');
var Q = require('q');

Expand Down Expand Up @@ -36,5 +37,39 @@ function userCanUpdateOrDeleteShoppingList(account, shoppingList) {
return userHasRight;
}

function authorise(req, res, next) {
var apiAccessToken = req.body.apiAccessToken || null;
var userId = req.params.userId || null;
if (apiAccessToken && userId) {
SecurityToken.authorise(apiAccessToken, userId)
.then(function(authorised) {
if (authorised) {
next();
}
else {
logger.log('info', 'User ' + userId + ' is not authorised. Request from address ' + req.connection.remoteAddress + '.');
res.json(401, {
error: "User is not authorised"
});
}
}, function(err) {
logger.log('error', 'An error has occurred while processing a request ' +
' from ' +
req.connection.remoteAddress + '. Stack trace: ' + err.stack);
res.json(500, {
error: err.message
});
});
}
else {
logger.log('info', 'Bad request from ' +
req.connection.remoteAddress + '. Api access token and user id are mandatory.');
res.json(400, {
error: 'Api access token and user id are mandatory.'
});
}
}

exports.authorise = authorise;
exports.userCanFetchShoppingList = userCanFetchShoppingList;
exports.userCanUpdateOrDeleteShoppingList = userCanUpdateOrDeleteShoppingList;
45 changes: 33 additions & 12 deletions test/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,39 @@

describe('Routing', function() {
var url;
url = 'http://localhost:3000';
url = 'http://localhost:3000';
var testUsername = makeid();
var testToDeleteUsername = makeid();
var testUserId;
var apiAccessToken;
var loginDone = false;
var fbUserId;
// Cloud 9
//url = 'https://project-livec93b91f71eb7.rhcloud.com';
//url = 'http://shopwithme.vgheri.c9.io';
before(function(done) {
if (mongoose.connection.readyState === 0) {
mongoose.connect(config.db.mongodb);
}
done();
});
if (mongoose.connection.readyState === 0) {
mongoose.connect(config.db.mongodb);
}
var loginCredentials = {
fbToken: 'CAACEdEose0cBAKk8ytcnJH4eLai9J3NTny7lTBU56N34XPWDlcAckT475csHugwNa0f6comqPjeLFVwixBeq2jOYitrixwdFstZBmlvtcfgjplrRU60Cn5s1RGZB7pBlAptbIqz6AePOG2gsG2DxDnnDh009GzXHuONlgn2I25b84obWkuVaG2GVL0PfgZD',
appName: 'testFBMobile'
};
request(url)
.post('/api/auth/facebook/mobile')
.send(loginCredentials)
.expect(200)
.end(function(err, res) {
if (err) {
throw err;
}
apiAccessToken = res.body.apiAccessToken;
fbUserId = res.body.userId; // I should use the fbUserId as the user id of all of my requests!
loginDone = true;
done();
});
//done();
});/*
describe('Account', function() {
it('should return error trying to save account without username', function(done) {
var profile = {
Expand Down Expand Up @@ -220,15 +240,15 @@
});
});
});
});
}); */
describe('Shopping List', function() {
var userId;
var shoppingListId;
var templateId;
var itemId;
before(function(done) {
//userId = new mongoose.Types.ObjectId('5149d6d382d09b6722000002');
userId = testUserId;
userId = fbUserId;
done();
});
it('should save a new empty shopping list',
Expand Down Expand Up @@ -256,6 +276,7 @@
function(done) {
request(url)
.get('/api/profiles/' + userId + '/lists/' + shoppingListId)
.send({apiAccessToken: apiAccessToken})
.expect('Content-Type', /json/)
.end(function(err,res) {
if (err) {
Expand All @@ -267,7 +288,7 @@
res.body.should.have.property('title', 'Test list');
done();
});
});
}); /*
it('should have added the newly created list to the lists of the user ' + testUsername,
function(done) {
request(url)
Expand Down Expand Up @@ -483,7 +504,7 @@
done();
});
});
/* Tests for list items */
// Tests for list items
it('should add a new item to the empty shopping list just created',
function(done) {
var listItem = {
Expand Down Expand Up @@ -679,7 +700,7 @@
}
});
});
/* End of tests for list items */
// End of tests for list items //
it('should return a 404 status code trying to delete an unknown shopping list',
function(done){
request(url)
Expand Down Expand Up @@ -735,7 +756,7 @@
done();
});
});
});
});*/
});
});

Expand Down

0 comments on commit b60114d

Please sign in to comment.