From f2dac093b27cc2cdeea6b01c5720121e40e374d3 Mon Sep 17 00:00:00 2001 From: mleanos Date: Sat, 9 Apr 2016 21:43:56 -0700 Subject: [PATCH 01/16] feat(core): JWT Authentication simplified Implements JWT Authentication, and removes dependency of session storage. Closes #389 --- config/lib/authorization.js | 35 +++++++++++ config/lib/express.js | 22 +++++-- config/lib/socket.io.js | 45 ++++---------- .../client/config/core.client.route-filter.js | 7 ++- .../auth-interceptor.client.service.js | 6 +- .../services/socket.io.client.service.js | 2 +- .../core/client/views/header.client.view.html | 2 +- .../authentication.client.controller.js | 8 +-- .../controllers/password.client.controller.js | 4 +- .../services/authentication.client.service.js | 61 ++++++++++++++++++- modules/users/server/config/strategies/jwt.js | 32 ++++++++++ .../server/config/users.server.config.js | 15 ----- .../users.authentication.server.controller.js | 38 +++--------- .../users/users.password.server.controller.js | 29 +++------ .../users/users.profile.server.controller.js | 8 +-- package.json | 1 + 16 files changed, 192 insertions(+), 123 deletions(-) create mode 100644 config/lib/authorization.js create mode 100644 modules/users/server/config/strategies/jwt.js diff --git a/config/lib/authorization.js b/config/lib/authorization.js new file mode 100644 index 0000000000..39d1ce03b9 --- /dev/null +++ b/config/lib/authorization.js @@ -0,0 +1,35 @@ +'use strict'; + +var config = require('../config'), + jwt = require('jsonwebtoken'), + lodash = require('lodash'); + +var auth = { + signToken: signToken +}; + +// Export the token auth service +module.exports = auth; + +// Sign the Token +function signToken(user, options) { + var payload, + token, + jwtOptions; + + if (!user || !user._id) { + return null; + } + + options = options || {}; + + payload = { + user: user._id.toString() + }; + + jwtOptions = lodash.merge(config.jwt.options, options); + + token = jwt.sign(payload, config.jwt.secret, jwtOptions); + + return token; +} diff --git a/config/lib/express.js b/config/lib/express.js index 93303517b6..6ef20a72b8 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -18,8 +18,8 @@ var config = require('../config'), flash = require('connect-flash'), hbs = require('express-hbs'), path = require('path'), - _ = require('lodash'), - lusca = require('lusca'); + lusca = require('lusca'), + passport = require('passport'); /** * Initialize local variables @@ -89,6 +89,21 @@ module.exports.initMiddleware = function (app) { // Add the cookie parser and flash middleware app.use(cookieParser()); app.use(flash()); + + // Authorize JWT + app.use(function (req, res, next) { + passport.authenticate('jwt', { session: false }, function (err, user) { + if (err) { + return next(new Error(err)); + } + + if (user) { + req.user = user; + } + + next(); + })(req, res, next); + }); }; /** @@ -238,9 +253,6 @@ module.exports.init = function (db) { // Initialize modules static client routes, before session! this.initModulesClientRoutes(app); - // Initialize Express session - this.initSession(app, db); - // Initialize Modules configuration this.initModulesConfiguration(app); diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js index 0050f4fb86..0a46149ae9 100644 --- a/config/lib/socket.io.js +++ b/config/lib/socket.io.js @@ -6,11 +6,9 @@ var config = require('../config'), fs = require('fs'), http = require('http'), https = require('https'), - cookieParser = require('cookie-parser'), passport = require('passport'), socketio = require('socket.io'), - session = require('express-session'), - MongoStore = require('connect-mongo')(session); + ExtractJwt = require('passport-jwt').ExtractJwt; // Define the Socket.io configuration method module.exports = function (app, db) { @@ -69,40 +67,21 @@ module.exports = function (app, db) { // Create a new Socket.io server var io = socketio.listen(server); - // Create a MongoDB storage object - var mongoStore = new MongoStore({ - mongooseConnection: db.connection, - collection: config.sessionCollection - }); - // Intercept Socket.io's handshake request io.use(function (socket, next) { - // Use the 'cookie-parser' module to parse the request cookies - cookieParser(config.sessionSecret)(socket.request, {}, function (err) { - // Get the session id from the request cookies - var sessionId = socket.request.signedCookies ? socket.request.signedCookies[config.sessionKey] : undefined; - - if (!sessionId) return next(new Error('sessionId was not found in socket.request'), false); - - // Use the mongoStorage instance to get the Express session information - mongoStore.get(sessionId, function (err, session) { - if (err) return next(err, false); - if (!session) return next(new Error('session was not found for ' + sessionId), false); + // Use Passport to populate the user details + passport.initialize()(socket.request, {}, function () { + passport.authenticate('jwt', { session: false }, function (err, user) { + if (err) { + return next(new Error(err)); + } - // Set the Socket.io session information - socket.request.session = session; + if (user) { + socket.request.user = user; + } - // Use Passport to populate the user details - passport.initialize()(socket.request, {}, function () { - passport.session()(socket.request, {}, function () { - if (socket.request.user) { - next(null, true); - } else { - next(new Error('User is not authenticated'), false); - } - }); - }); - }); + next(); + })(socket.request, socket.request.res, next); }); }); diff --git a/modules/core/client/config/core.client.route-filter.js b/modules/core/client/config/core.client.route-filter.js index 4d7c605492..d56518c701 100644 --- a/modules/core/client/config/core.client.route-filter.js +++ b/modules/core/client/config/core.client.route-filter.js @@ -8,7 +8,12 @@ routeFilter.$inject = ['$rootScope', '$state', 'Authentication']; function routeFilter($rootScope, $state, Authentication) { - $rootScope.$on('$stateChangeStart', stateChangeStart); + + Authentication.ready + .then(function (auth) { + $rootScope.$on('$stateChangeStart', stateChangeStart); + }); + $rootScope.$on('$stateChangeSuccess', stateChangeSuccess); function stateChangeStart(event, toState, toParams, fromState, fromParams) { diff --git a/modules/core/client/services/interceptors/auth-interceptor.client.service.js b/modules/core/client/services/interceptors/auth-interceptor.client.service.js index 89bae6f023..37aa6212db 100644 --- a/modules/core/client/services/interceptors/auth-interceptor.client.service.js +++ b/modules/core/client/services/interceptors/auth-interceptor.client.service.js @@ -5,9 +5,9 @@ .module('core') .factory('authInterceptor', authInterceptor); - authInterceptor.$inject = ['$q', '$injector', 'Authentication']; + authInterceptor.$inject = ['$q', '$injector']; - function authInterceptor($q, $injector, Authentication) { + function authInterceptor($q, $injector) { var service = { responseError: responseError }; @@ -22,7 +22,7 @@ break; case 401: // Deauthenticate the global user - Authentication.user = null; + $injector.get('Authentication').user = null; $injector.get('$state').transitionTo('authentication.signin'); break; case 403: diff --git a/modules/core/client/services/socket.io.client.service.js b/modules/core/client/services/socket.io.client.service.js index a00299ae8a..9d94e6f835 100644 --- a/modules/core/client/services/socket.io.client.service.js +++ b/modules/core/client/services/socket.io.client.service.js @@ -25,7 +25,7 @@ function connect() { // Connect only when authenticated if (Authentication.user) { - service.socket = io(); + service.socket = io('', { query: 'auth_token=' + Authentication.token }); } } diff --git a/modules/core/client/views/header.client.view.html b/modules/core/client/views/header.client.view.html index d35f961dc5..224f198f66 100644 --- a/modules/core/client/views/header.client.view.html +++ b/modules/core/client/views/header.client.view.html @@ -41,7 +41,7 @@
  • - Signout + Signout
  • diff --git a/modules/users/client/controllers/authentication.client.controller.js b/modules/users/client/controllers/authentication.client.controller.js index 23379ce384..9d13f4aedd 100644 --- a/modules/users/client/controllers/authentication.client.controller.js +++ b/modules/users/client/controllers/authentication.client.controller.js @@ -66,8 +66,8 @@ // Authentication Callbacks function onUserSignupSuccess(response) { - // If successful we assign the response to the global user model - vm.authentication.user = response; + // If successful we login the user client-side using the JWT token + Authentication.login(response.user, response.token); Notification.success({ message: ' Signup successful!' }); // And redirect to the previous or home page $state.go($state.previous.state.name || 'home', $state.previous.params); @@ -78,8 +78,8 @@ } function onUserSigninSuccess(response) { - // If successful we assign the response to the global user model - vm.authentication.user = response; + // If successful we login the user client-side using the JWT Token + Authentication.login(response.user, response.token); Notification.info({ message: 'Welcome ' + response.firstName }); // And redirect to the previous or home page $state.go($state.previous.state.name || 'home', $state.previous.params); diff --git a/modules/users/client/controllers/password.client.controller.js b/modules/users/client/controllers/password.client.controller.js index 856d87f7ff..c82ce8425b 100644 --- a/modules/users/client/controllers/password.client.controller.js +++ b/modules/users/client/controllers/password.client.controller.js @@ -66,8 +66,8 @@ // If successful show success message and clear form vm.passwordDetails = null; - // Attach user profile - Authentication.user = response; + // Login the user + Authentication.login(response.user, response.token); Notification.success({ message: ' Password reset successful!' }); // And redirect to the index page $location.path('/password/reset/success'); diff --git a/modules/users/client/services/authentication.client.service.js b/modules/users/client/services/authentication.client.service.js index 9e6b83d663..0ced369661 100644 --- a/modules/users/client/services/authentication.client.service.js +++ b/modules/users/client/services/authentication.client.service.js @@ -7,13 +7,68 @@ .module('users.services') .factory('Authentication', Authentication); - Authentication.$inject = ['$window']; + Authentication.$inject = ['$window', '$state', '$http', '$location', '$q', 'UsersService']; + + function Authentication($window, $state, $http, $location, $q, UsersService) { + var readyPromise = $q.defer(); - function Authentication($window) { var auth = { - user: $window.user + user: null, + token: null, + login: login, + signout: signout, + refresh: refresh, + ready: readyPromise.promise }; + // Initialize service + init(); + return auth; + + function init() { + var token = localStorage.getItem('token') || $location.search().token || null; + // Remove the token from the URL if present + $location.search('token', null); + + if (token) { + auth.token = token; + $http.defaults.headers.common.Authorization = 'JWT ' + token; + refresh(); + } else { + readyPromise.resolve(auth); + } + } + + function login(user, token) { + auth.user = user; + auth.token = token; + + localStorage.setItem('token', token); + $http.defaults.headers.common.Authorization = 'JWT ' + token; + + readyPromise.resolve(auth); + } + + function signout() { + localStorage.removeItem('token'); + auth.user = null; + auth.token = null; + + $state.go('home', { reload: true }); + } + + function refresh(requestFromServer, callback) { + readyPromise = $q.defer(); + + UsersService.me().$promise + .then(function (user) { + auth.user = user; + readyPromise.resolve(auth); + }) + .catch(function (errorResponse) { + readyPromise.reject(errorResponse); + }); + } } }()); diff --git a/modules/users/server/config/strategies/jwt.js b/modules/users/server/config/strategies/jwt.js new file mode 100644 index 0000000000..5a11f3551b --- /dev/null +++ b/modules/users/server/config/strategies/jwt.js @@ -0,0 +1,32 @@ +'use strict'; + +/** + * Module dependencies + */ +var passport = require('passport'), + JwtStrategy = require('passport-jwt').Strategy, + ExtractJwt = require('passport-jwt').ExtractJwt, + User = require('mongoose').model('User'); + +module.exports = function (config) { + var opts = { + jwtFromRequest: ExtractJwt.versionOneCompatibility({ tokenQueryParameterName: 'auth_token' }), + secretOrKey: config.jwt.secret + // opts.issuer = "accounts.examplesoft.com", + // opts.audience = "yoursite.net" + }; + + passport.use(new JwtStrategy(opts, function (jwt_payload, done) { + User.findById({ _id: jwt_payload.user }, '-salt -password', function (err, user) { + if (err) { + return done(err, false); + } + + if (!user) { + return done('User not found'); + } + + return done(null, user); + }); + })); +}; diff --git a/modules/users/server/config/users.server.config.js b/modules/users/server/config/users.server.config.js index 2bfedceb01..0f5811bc45 100644 --- a/modules/users/server/config/users.server.config.js +++ b/modules/users/server/config/users.server.config.js @@ -12,20 +12,6 @@ var passport = require('passport'), * Module init function */ module.exports = function (app, db) { - // Serialize sessions - passport.serializeUser(function (user, done) { - done(null, user.id); - }); - - // Deserialize sessions - passport.deserializeUser(function (id, done) { - User.findOne({ - _id: id - }, '-salt -password', function (err, user) { - done(err, user); - }); - }); - // Initialize strategies config.utils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function (strategy) { require(path.resolve(strategy))(config); @@ -33,5 +19,4 @@ module.exports = function (app, db) { // Add passport's middleware app.use(passport.initialize()); - app.use(passport.session()); }; diff --git a/modules/users/server/controllers/users/users.authentication.server.controller.js b/modules/users/server/controllers/users/users.authentication.server.controller.js index 75373b470d..fe12d3ab9d 100644 --- a/modules/users/server/controllers/users/users.authentication.server.controller.js +++ b/modules/users/server/controllers/users/users.authentication.server.controller.js @@ -7,7 +7,8 @@ var path = require('path'), errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), mongoose = require('mongoose'), passport = require('passport'), - User = mongoose.model('User'); + User = mongoose.model('User'), + authorization = require(path.resolve('./config/lib/authorization')); // URLs for which user can't be redirected on signin var noReturnUrls = [ @@ -38,13 +39,8 @@ exports.signup = function (req, res) { user.password = undefined; user.salt = undefined; - req.login(user, function (err) { - if (err) { - res.status(400).send(err); - } else { - res.json(user); - } - }); + var token = authorization.signToken(user); + res.json({ user: user, token: token }); } }); }; @@ -53,7 +49,7 @@ exports.signup = function (req, res) { * Signin after passport authentication */ exports.signin = function (req, res, next) { - passport.authenticate('local', function (err, user, info) { + passport.authenticate('local', function (err, user) { if (err || !user) { res.status(422).send(info); } else { @@ -61,13 +57,8 @@ exports.signin = function (req, res, next) { user.password = undefined; user.salt = undefined; - req.login(user, function (err) { - if (err) { - res.status(400).send(err); - } else { - res.json(user); - } - }); + var token = authorization.signToken(user); + res.json({ user: user, token: token }); } })(req, res, next); }; @@ -107,12 +98,9 @@ exports.oauthCallback = function (strategy) { if (!user) { return res.redirect('/authentication/signin'); } - req.login(user, function (err) { - if (err) { - return res.redirect('/authentication/signin'); - } - return res.redirect(info.redirect_to || '/'); + var token = authorization.signToken(user); + return res.redirect(info.redirect_to || '/'); }); })(req, res, next); }; @@ -237,13 +225,7 @@ exports.removeOAuthProvider = function (req, res, next) { message: errorHandler.getErrorMessage(err) }); } else { - req.login(user, function (err) { - if (err) { - return res.status(400).send(err); - } else { - return res.json(user); - } - }); + return res.json(user); } }); }; diff --git a/modules/users/server/controllers/users/users.password.server.controller.js b/modules/users/server/controllers/users/users.password.server.controller.js index 7f3153ba1f..0465aa1111 100644 --- a/modules/users/server/controllers/users/users.password.server.controller.js +++ b/modules/users/server/controllers/users/users.password.server.controller.js @@ -10,7 +10,8 @@ var path = require('path'), User = mongoose.model('User'), nodemailer = require('nodemailer'), async = require('async'), - crypto = require('crypto'); + crypto = require('crypto'), + authorization = require(path.resolve('./config/lib/authorization')); var smtpTransport = nodemailer.createTransport(config.mailer.options); @@ -145,19 +146,13 @@ exports.reset = function (req, res, next) { message: errorHandler.getErrorMessage(err) }); } else { - req.login(user, function (err) { - if (err) { - res.status(400).send(err); - } else { - // Remove sensitive data before return authenticated user - user.password = undefined; - user.salt = undefined; + user.password = undefined; + user.salt = undefined; - res.json(user); + var token = authorization.signToken(user); + res.json({ user: user, token: token }); - done(err, user); - } - }); + done(err, user); } }); } else { @@ -221,14 +216,8 @@ exports.changePassword = function (req, res, next) { message: errorHandler.getErrorMessage(err) }); } else { - req.login(user, function (err) { - if (err) { - res.status(400).send(err); - } else { - res.send({ - message: 'Password changed successfully' - }); - } + res.send({ + message: 'Password changed successfully' }); } }); diff --git a/modules/users/server/controllers/users/users.profile.server.controller.js b/modules/users/server/controllers/users/users.profile.server.controller.js index 52fc23cdc7..103ece7d9a 100644 --- a/modules/users/server/controllers/users/users.profile.server.controller.js +++ b/modules/users/server/controllers/users/users.profile.server.controller.js @@ -35,13 +35,7 @@ exports.update = function (req, res) { message: errorHandler.getErrorMessage(err) }); } else { - req.login(user, function (err) { - if (err) { - res.status(400).send(err); - } else { - res.json(user); - } - }); + res.json(user); } }); } else { diff --git a/package.json b/package.json index 05802e9320..e0dfba5016 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "passport-facebook": "~2.1.0", "passport-github": "~1.1.0", "passport-google-oauth": "~1.0.0", + "passport-jwt": "~2.0.0", "passport-linkedin": "~1.0.0", "passport-local": "~1.0.0", "passport-paypal-openidconnect": "~0.1.1", From 683df15c8b022f4c1f2d4031226a450745d845cf Mon Sep 17 00:00:00 2001 From: mleanos Date: Sat, 9 Apr 2016 22:03:33 -0700 Subject: [PATCH 02/16] feat(tests): Server tests with JWT Auth Updates the necessary server-side tests to accommodate the JWT Authentication. --- .../server/article.server.routes.tests.js | 7 +++++ .../tests/server/user.server.routes.tests.js | 27 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/modules/articles/tests/server/article.server.routes.tests.js b/modules/articles/tests/server/article.server.routes.tests.js index 6f35907e6a..a831c27451 100644 --- a/modules/articles/tests/server/article.server.routes.tests.js +++ b/modules/articles/tests/server/article.server.routes.tests.js @@ -70,6 +70,7 @@ describe('Article CRUD tests', function () { } agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(403) .end(function (articleSaveErr, articleSaveRes) { @@ -101,6 +102,7 @@ describe('Article CRUD tests', function () { } agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(403) .end(function (articleSaveErr, articleSaveRes) { @@ -181,6 +183,7 @@ describe('Article CRUD tests', function () { } agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(403) .end(function (articleSaveErr, articleSaveRes) { @@ -252,6 +255,7 @@ describe('Article CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleSaveErr, articleSaveRes) { @@ -279,6 +283,7 @@ describe('Article CRUD tests', function () { // Get the article agent.get('/api/articles/' + articleSaveRes.body._id) + .set('Authorization', 'JWT ' + res.body.token) .expect(200) .end(function (articleInfoErr, articleInfoRes) { // Handle article error @@ -359,6 +364,7 @@ describe('Article CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleSaveErr, articleSaveRes) { @@ -384,6 +390,7 @@ describe('Article CRUD tests', function () { // Get the article agent.get('/api/articles/' + articleSaveRes.body._id) + .set('Authorization', 'JWT ' + res.body.token) .expect(200) .end(function (articleInfoErr, articleInfoRes) { // Handle article error diff --git a/modules/users/tests/server/user.server.routes.tests.js b/modules/users/tests/server/user.server.routes.tests.js index 6a51d63362..2e1a6acf2d 100644 --- a/modules/users/tests/server/user.server.routes.tests.js +++ b/modules/users/tests/server/user.server.routes.tests.js @@ -79,13 +79,13 @@ describe('User CRUD tests', function () { return done(signupErr); } - signupRes.body.username.should.equal(_user.username); - signupRes.body.email.should.equal(_user.email); + signupRes.body.user.username.should.equal(_user.username); + signupRes.body.user.email.should.equal(_user.email); // Assert a proper profile image has been set, even if by default - signupRes.body.profileImageURL.should.not.be.empty(); + signupRes.body.user.profileImageURL.should.not.be.empty(); // Assert we have just the default 'user' role - signupRes.body.roles.should.be.instanceof(Array).and.have.lengthOf(1); - signupRes.body.roles.indexOf('user').should.equal(0); + signupRes.body.user.roles.should.be.instanceof(Array).and.have.lengthOf(1); + signupRes.body.user.roles.indexOf('user').should.equal(0); return done(); }); }); @@ -103,6 +103,7 @@ describe('User CRUD tests', function () { // Logout agent.get('/api/auth/signout') + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(302) .end(function (signoutErr, signoutRes) { if (signoutErr) { @@ -170,6 +171,7 @@ describe('User CRUD tests', function () { // Request list of users agent.get('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(403) .end(function (usersGetErr, usersGetRes) { if (usersGetErr) { @@ -197,6 +199,7 @@ describe('User CRUD tests', function () { // Request list of users agent.get('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(200) .end(function (usersGetErr, usersGetRes) { if (usersGetErr) { @@ -228,6 +231,7 @@ describe('User CRUD tests', function () { // Get single user information from the database agent.get('/api/users/' + user._id) + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(200) .end(function (userInfoErr, userInfoRes) { if (userInfoErr) { @@ -267,6 +271,7 @@ describe('User CRUD tests', function () { }; agent.put('/api/users/' + user._id) + .set('Authorization', 'JWT ' + signinRes.body.token) .send(userUpdate) .expect(200) .end(function (userInfoErr, userInfoRes) { @@ -302,6 +307,7 @@ describe('User CRUD tests', function () { } agent.delete('/api/users/' + user._id) + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(200) .end(function (userInfoErr, userInfoRes) { if (userInfoErr) { @@ -496,6 +502,7 @@ describe('User CRUD tests', function () { // Change password agent.post('/api/users/password') + .set('Authorization', 'JWT ' + signinRes.body.token) .send({ newPassword: '1234567890Aa$', verifyPassword: '1234567890Aa$', @@ -525,6 +532,7 @@ describe('User CRUD tests', function () { // Change password agent.post('/api/users/password') + .set('Authorization', 'JWT ' + signinRes.body.token) .send({ newPassword: '1234567890Aa$', verifyPassword: '1234567890-ABC-123-Aa$', @@ -554,6 +562,7 @@ describe('User CRUD tests', function () { // Change password agent.post('/api/users/password') + .set('Authorization', 'JWT ' + signinRes.body.token) .send({ newPassword: '1234567890Aa$', verifyPassword: '1234567890Aa$', @@ -583,6 +592,7 @@ describe('User CRUD tests', function () { // Change password agent.post('/api/users/password') + .set('Authorization', 'JWT ' + signinRes.body.token) .send({ newPassword: '', verifyPassword: '', @@ -632,6 +642,7 @@ describe('User CRUD tests', function () { // Get own user details agent.get('/api/users/me') + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(200) .end(function (err, res) { if (err) { @@ -682,6 +693,7 @@ describe('User CRUD tests', function () { }; agent.put('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(userUpdate) .expect(200) .end(function (userInfoErr, userInfoRes) { @@ -724,6 +736,7 @@ describe('User CRUD tests', function () { }; agent.put('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(userUpdate) .expect(200) .end(function (userInfoErr, userInfoRes) { @@ -781,6 +794,7 @@ describe('User CRUD tests', function () { }; agent.put('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(userUpdate) .expect(422) .end(function (userInfoErr, userInfoRes) { @@ -833,6 +847,7 @@ describe('User CRUD tests', function () { }; agent.put('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(userUpdate) .expect(422) .end(function (userInfoErr, userInfoRes) { @@ -953,6 +968,7 @@ describe('User CRUD tests', function () { } agent.post('/api/users/picture') + .set('Authorization', 'JWT ' + signinRes.body.token) .attach('newProfilePicture', './modules/users/client/img/profile/default.png') .expect(200) .end(function (userInfoErr, userInfoRes) { @@ -981,6 +997,7 @@ describe('User CRUD tests', function () { } agent.post('/api/users/picture') + .set('Authorization', 'JWT ' + signinRes.body.token) .attach('fieldThatDoesntWork', './modules/users/client/img/profile/default.png') .send(credentials) .expect(422) From 1372bb7d0446da2ea90ce03fa22119cea337b778 Mon Sep 17 00:00:00 2001 From: mleanos Date: Sun, 10 Apr 2016 14:37:12 -0700 Subject: [PATCH 03/16] Fixed client-side tests to accommodate the JWT Authentication. Not really sure about the $httpBackend expects & flushes that had to be added. It seems like we shouldn't have to do this for every client-side tests. The issue is that the Authentication service now makes a call to the backend (`api/users/me`) to retrieve the user info. Could use some eyes on this aspect of the implementation. --- .../articles.client.controller.tests.js | 2 ++ .../client/articles.client.routes.tests.js | 4 ++++ .../list-articles.client.controller.tests.js | 4 ++++ .../authentication.client.controller.tests.js | 9 +++++++- ...ssword-validator.client.directive.tests.js | 7 ++++++- .../password-verify.client.directive.tests.js | 7 ++++++- .../password.client.controller.tests.js | 21 +++++++++++++++++-- 7 files changed, 49 insertions(+), 5 deletions(-) diff --git a/modules/articles/tests/client/articles.client.controller.tests.js b/modules/articles/tests/client/articles.client.controller.tests.js index 966d9c4309..7e54e86517 100644 --- a/modules/articles/tests/client/articles.client.controller.tests.js +++ b/modules/articles/tests/client/articles.client.controller.tests.js @@ -46,6 +46,8 @@ Authentication = _Authentication_; ArticlesService = _ArticlesService_; + $httpBackend.whenGET('api/users/me').respond({}); + // create mock article mockArticle = new ArticlesService({ _id: '525a8422f6d0f87f0e407a33', diff --git a/modules/articles/tests/client/articles.client.routes.tests.js b/modules/articles/tests/client/articles.client.routes.tests.js index 7b724a5c53..8fe68252c9 100644 --- a/modules/articles/tests/client/articles.client.routes.tests.js +++ b/modules/articles/tests/client/articles.client.routes.tests.js @@ -18,6 +18,10 @@ ArticlesService = _ArticlesService_; })); + afterEach(inject(function (Authentication) { + Authentication.signout(); + })); + describe('Route Config', function () { describe('Main Route', function () { var mainstate; diff --git a/modules/articles/tests/client/list-articles.client.controller.tests.js b/modules/articles/tests/client/list-articles.client.controller.tests.js index b5e37c9f62..8486bede31 100644 --- a/modules/articles/tests/client/list-articles.client.controller.tests.js +++ b/modules/articles/tests/client/list-articles.client.controller.tests.js @@ -67,6 +67,10 @@ spyOn($state, 'go'); })); + afterEach(inject(function (Authentication) { + Authentication.signout(); + })); + describe('Instantiate', function () { var mockArticleList; diff --git a/modules/users/tests/client/authentication.client.controller.tests.js b/modules/users/tests/client/authentication.client.controller.tests.js index f2e99e1fec..a65d489d44 100644 --- a/modules/users/tests/client/authentication.client.controller.tests.js +++ b/modules/users/tests/client/authentication.client.controller.tests.js @@ -51,6 +51,8 @@ $httpBackend.whenGET('/modules/core/client/views/home.client.view.html').respond(200); $httpBackend.whenGET('/modules/core/client/views/400.client.view.html').respond(200); + $httpBackend.when('GET', 'api/users/me').respond(200, {}); + // Initialize the Authentication controller AuthenticationController = $controller('AuthenticationController as vm', { $scope: scope @@ -76,6 +78,7 @@ $templateCache.put('/modules/core/client/views/home.client.view.html', ''); // Test expected GET request $httpBackend.when('POST', '/api/auth/signin').respond(200, { email: 'Fred@email.com' }); + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); scope.vm.signin(true); $httpBackend.flush(); @@ -100,7 +103,8 @@ spyOn($state, 'go'); // Test expected GET request - $httpBackend.when('POST', '/api/auth/signin').respond(200, 'Fred'); + $httpBackend.when('POST', '/api/auth/signin').respond(200, { user: 'Fred' }); + $httpBackend.when('GET', 'api/users/me').respond(200, { user: 'Fred' }); scope.vm.signin(true); $httpBackend.flush(); @@ -149,6 +153,7 @@ // Test expected GET request scope.vm.authentication.user = 'Fred'; $httpBackend.when('POST', '/api/auth/signup').respond(200, { username: 'Fred' }); + $httpBackend.when('GET', 'api/users/me').respond(200, { user: 'Fred' }); scope.vm.signup(true); $httpBackend.flush(); @@ -181,6 +186,8 @@ $location = _$location_; $location.path = jasmine.createSpy().and.returnValue(true); + $httpBackend.when('GET', 'api/users/me').respond(200, {}); + // Mock logged in user _Authentication_.user = { username: 'test', diff --git a/modules/users/tests/client/password-validator.client.directive.tests.js b/modules/users/tests/client/password-validator.client.directive.tests.js index c6f318349c..3e3f4e8f5a 100644 --- a/modules/users/tests/client/password-validator.client.directive.tests.js +++ b/modules/users/tests/client/password-validator.client.directive.tests.js @@ -7,15 +7,20 @@ var scope, element, $compile, + $httpBackend, form; // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(inject(function(_$rootScope_, _$compile_) { + beforeEach(inject(function(_$rootScope_, _$compile_, _$httpBackend_) { // Set a new global scope scope = _$rootScope_.$new(); $compile = _$compile_; + $httpBackend = _$httpBackend_; + + $httpBackend.whenGET('api/users/me').respond({}); + $httpBackend.flush(); scope.passwordMock = { password: 'P@ssw0rd!!' diff --git a/modules/users/tests/client/password-verify.client.directive.tests.js b/modules/users/tests/client/password-verify.client.directive.tests.js index 24b41dcd5a..b84d414b31 100644 --- a/modules/users/tests/client/password-verify.client.directive.tests.js +++ b/modules/users/tests/client/password-verify.client.directive.tests.js @@ -7,15 +7,20 @@ var scope, element, $compile, + $httpBackend, form; // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(inject(function(_$rootScope_, _$compile_) { + beforeEach(inject(function(_$rootScope_, _$compile_, _$httpBackend_) { // Set a new global scope scope = _$rootScope_.$new(); $compile = _$compile_; + $httpBackend = _$httpBackend_; + + $httpBackend.whenGET('api/users/me').respond({}); + $httpBackend.flush(); scope.passwordMock = { newPassword: 'P@ssw0rd!!', diff --git a/modules/users/tests/client/password.client.controller.tests.js b/modules/users/tests/client/password.client.controller.tests.js index 3bb87cc588..9c955b5644 100644 --- a/modules/users/tests/client/password.client.controller.tests.js +++ b/modules/users/tests/client/password.client.controller.tests.js @@ -6,6 +6,7 @@ // Initialize global variables var PasswordController, scope, + Authentication, $httpBackend, $stateParams, $location, @@ -35,6 +36,7 @@ scope = $rootScope.$new(); // Point global variables to injected services + Authentication = _Authentication_; $stateParams = _$stateParams_; $httpBackend = _$httpBackend_; $location = _$location_; @@ -42,9 +44,11 @@ // Ignore parent template gets on state transition $httpBackend.whenGET('/modules/core/client/views/404.client.view.html').respond(200); + $httpBackend.whenGET('api/users/me').respond({ user: { username: 'test', roles: ['user'] } }); + $httpBackend.flush(); // Mock logged in user - _Authentication_.user = { + Authentication.user = { username: 'test', roles: ['user'] }; @@ -55,6 +59,10 @@ }); })); + afterEach(inject(function (Authentication) { + Authentication.signout(); + })); + it('should redirect logged in user to home', function() { expect($location.path).toHaveBeenCalledWith('/'); }); @@ -66,6 +74,7 @@ scope = $rootScope.$new(); // Point global variables to injected services + Authentication = _Authentication_; $stateParams = _$stateParams_; $httpBackend = _$httpBackend_; $location = _$location_; @@ -77,6 +86,10 @@ spyOn(Notification, 'error'); spyOn(Notification, 'success'); + Authentication.user = null; + + $httpBackend.whenGET('api/users/me').respond({ user: null }); + // Ignore parent template gets on state transition $httpBackend.whenGET('/modules/core/client/views/404.client.view.html').respond(200); $httpBackend.whenGET('/modules/core/client/views/400.client.view.html').respond(200); @@ -87,6 +100,10 @@ }); })); + afterEach(inject(function (Authentication) { + Authentication.signout(); + })); + it('should not redirect to home', function() { expect($location.path).not.toHaveBeenCalledWith('/'); }); @@ -168,7 +185,7 @@ username: 'test' }; beforeEach(function() { - $httpBackend.when('POST', '/api/auth/reset/' + token, passwordDetails).respond(user); + $httpBackend.when('POST', '/api/auth/reset/' + token, passwordDetails).respond({ user: user }); scope.vm.resetUserPassword(true); $httpBackend.flush(); From e489336786ad2d50735e041ff74bf0e360fdfcf8 Mon Sep 17 00:00:00 2001 From: mleanos Date: Sun, 10 Apr 2016 16:36:08 -0700 Subject: [PATCH 04/16] feat(tests): E2E Test for JWT Fixed the E2E tests for the JWT implementation. --- modules/users/tests/e2e/users.e2e.tests.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/users/tests/e2e/users.e2e.tests.js b/modules/users/tests/e2e/users.e2e.tests.js index 02d27ede02..4907b2df18 100644 --- a/modules/users/tests/e2e/users.e2e.tests.js +++ b/modules/users/tests/e2e/users.e2e.tests.js @@ -22,6 +22,8 @@ describe('Users E2E Tests:', function () { browser.get('http://localhost:3001/authentication/signout'); // Delete all cookies browser.driver.manage().deleteAllCookies(); + // Clear local storage + browser.executeScript('localStorage.clear();'); }; describe('Signup Validation', function () { From 86a5ae4126275d4990d98016a2704388917cfaeb Mon Sep 17 00:00:00 2001 From: mleanos Date: Mon, 11 Apr 2016 16:27:22 -0700 Subject: [PATCH 05/16] signout link style Adds the "button" role to the signout link in the header. --- modules/core/client/views/header.client.view.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/client/views/header.client.view.html b/modules/core/client/views/header.client.view.html index 224f198f66..1a7c1a4919 100644 --- a/modules/core/client/views/header.client.view.html +++ b/modules/core/client/views/header.client.view.html @@ -41,7 +41,7 @@
  • - Signout + Signout
  • From c907167d4c246d1330cf82078cfc35c53302fd48 Mon Sep 17 00:00:00 2001 From: mleanos Date: Mon, 11 Apr 2016 18:55:24 -0700 Subject: [PATCH 06/16] SocketIO Authentication with Token Adds authentication to the SocketIO server using the JWT Token. Added the socketio-auth package to the project which provides hooks for handling authentication with SocketIO. Changed the Passport JWT Strategy to use the `fromAuthHeader()` method for getting the Token from the request. --- config/lib/socket.io.js | 53 +++++++++++-------- .../services/socket.io.client.service.js | 3 +- modules/users/server/config/strategies/jwt.js | 6 +-- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js index 0a46149ae9..ee7f10fdef 100644 --- a/config/lib/socket.io.js +++ b/config/lib/socket.io.js @@ -7,8 +7,7 @@ var config = require('../config'), http = require('http'), https = require('https'), passport = require('passport'), - socketio = require('socket.io'), - ExtractJwt = require('passport-jwt').ExtractJwt; + socketio = require('socket.io'); // Define the Socket.io configuration method module.exports = function (app, db) { @@ -67,30 +66,42 @@ module.exports = function (app, db) { // Create a new Socket.io server var io = socketio.listen(server); - // Intercept Socket.io's handshake request - io.use(function (socket, next) { + // Configure SocketIO Authentication + require('socketio-auth')(io, { + authenticate: authenticate, + postAuthenticate: postAuthenticate, + timeout: 1000 + }); + + return server; + + // Handler for authenticating the SocketIO connection + function authenticate(socket, data, callback) { + // Set the Authorization header using the provided token + socket.request.headers.authorization = 'JWT ' + data.token; + // Use Passport to populate the user details - passport.initialize()(socket.request, {}, function () { - passport.authenticate('jwt', { session: false }, function (err, user) { - if (err) { - return next(new Error(err)); - } + passport.authenticate('jwt', { session: false }, function (err, user) { + if (err) { + return callback(new Error(err)); + } - if (user) { - socket.request.user = user; - } + if (!user) { + return callback(new Error('User not found')); + } - next(); - })(socket.request, socket.request.res, next); - }); - }); + // Set the socket user + socket.request.user = user; - // Add an event listener to the 'connection' event - io.on('connection', function (socket) { + return callback(null, true); + })(socket.request, socket.request.res, callback); + } + + // Handler for post-Authentication + function postAuthenticate(socket, data) { + // Configure the server-side Socket listeners config.files.server.sockets.forEach(function (socketConfiguration) { require(path.resolve(socketConfiguration))(io, socket); }); - }); - - return server; + } }; diff --git a/modules/core/client/services/socket.io.client.service.js b/modules/core/client/services/socket.io.client.service.js index 9d94e6f835..666b54a02a 100644 --- a/modules/core/client/services/socket.io.client.service.js +++ b/modules/core/client/services/socket.io.client.service.js @@ -25,7 +25,8 @@ function connect() { // Connect only when authenticated if (Authentication.user) { - service.socket = io('', { query: 'auth_token=' + Authentication.token }); + service.socket = io(); + service.socket.emit('authentication', { token: Authentication.token }); } } diff --git a/modules/users/server/config/strategies/jwt.js b/modules/users/server/config/strategies/jwt.js index 5a11f3551b..badf620afa 100644 --- a/modules/users/server/config/strategies/jwt.js +++ b/modules/users/server/config/strategies/jwt.js @@ -10,14 +10,14 @@ var passport = require('passport'), module.exports = function (config) { var opts = { - jwtFromRequest: ExtractJwt.versionOneCompatibility({ tokenQueryParameterName: 'auth_token' }), + jwtFromRequest: ExtractJwt.fromAuthHeader(), secretOrKey: config.jwt.secret // opts.issuer = "accounts.examplesoft.com", // opts.audience = "yoursite.net" }; - passport.use(new JwtStrategy(opts, function (jwt_payload, done) { - User.findById({ _id: jwt_payload.user }, '-salt -password', function (err, user) { + passport.use(new JwtStrategy(opts, function (jwtPayload, done) { + User.findById({ _id: jwtPayload.user }, '-salt -password', function (err, user) { if (err) { return done(err, false); } From 546cf02ab0aa1e6661a65b636fef09ca63c70d20 Mon Sep 17 00:00:00 2001 From: mleanos Date: Wed, 13 Apr 2016 01:03:05 -0700 Subject: [PATCH 07/16] Mock Socket server authentication Adds a listener to the mock Socket.IO server used with the Karma tests. --- modules/core/tests/client/socket.io.client.service.tests.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/core/tests/client/socket.io.client.service.tests.js b/modules/core/tests/client/socket.io.client.service.tests.js index 02def5d065..587b9342fd 100644 --- a/modules/core/tests/client/socket.io.client.service.tests.js +++ b/modules/core/tests/client/socket.io.client.service.tests.js @@ -24,6 +24,10 @@ function connect() { io.socket = {}; + + // Mock authentication + io.on('authentication', function (msg, data) { + }); } function emit(msg, data) { From 98e27c303340af9aa1ce88ec5d1d38be0b5d6999 Mon Sep 17 00:00:00 2001 From: mleanos Date: Tue, 16 Aug 2016 18:02:32 -0700 Subject: [PATCH 08/16] Lingering issues after rebase Fixed lingering issues after rebasing. Fixed client-side tests that were failing. Added authorization header to server-side tests for the Admin Articles module Added Authentication mocking for edit-profile client controller test --- config/lib/express.js | 1 + .../tests/server/admin.article.server.routes.tests.js | 8 ++++++++ .../tests/client/edit-profile.client.controller.tests.js | 6 ++++++ .../client/password-validator.client.directive.tests.js | 1 - .../client/password-verify.client.directive.tests.js | 1 - .../tests/client/password.client.controller.tests.js | 1 - 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/config/lib/express.js b/config/lib/express.js index 6ef20a72b8..639fc854b1 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -18,6 +18,7 @@ var config = require('../config'), flash = require('connect-flash'), hbs = require('express-hbs'), path = require('path'), + _ = require('lodash'), lusca = require('lusca'), passport = require('passport'); diff --git a/modules/articles/tests/server/admin.article.server.routes.tests.js b/modules/articles/tests/server/admin.article.server.routes.tests.js index 0322dd14bc..ebe6dff3c8 100644 --- a/modules/articles/tests/server/admin.article.server.routes.tests.js +++ b/modules/articles/tests/server/admin.article.server.routes.tests.js @@ -74,6 +74,7 @@ describe('Article Admin CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleSaveErr, articleSaveRes) { @@ -119,6 +120,7 @@ describe('Article Admin CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleSaveErr, articleSaveRes) { @@ -132,6 +134,7 @@ describe('Article Admin CRUD tests', function () { // Update an existing article agent.put('/api/articles/' + articleSaveRes.body._id) + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleUpdateErr, articleUpdateRes) { @@ -169,6 +172,7 @@ describe('Article Admin CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(422) .end(function (articleSaveErr, articleSaveRes) { @@ -196,6 +200,7 @@ describe('Article Admin CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleSaveErr, articleSaveRes) { @@ -206,6 +211,7 @@ describe('Article Admin CRUD tests', function () { // Delete an existing article agent.delete('/api/articles/' + articleSaveRes.body._id) + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleDeleteErr, articleDeleteRes) { @@ -243,6 +249,7 @@ describe('Article Admin CRUD tests', function () { // Save a new article agent.post('/api/articles') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(article) .expect(200) .end(function (articleSaveErr, articleSaveRes) { @@ -253,6 +260,7 @@ describe('Article Admin CRUD tests', function () { // Get the article agent.get('/api/articles/' + articleSaveRes.body._id) + .set('Authorization', 'JWT ' + signinRes.body.token) .expect(200) .end(function (articleInfoErr, articleInfoRes) { // Handle article error diff --git a/modules/users/tests/client/edit-profile.client.controller.tests.js b/modules/users/tests/client/edit-profile.client.controller.tests.js index e608a1bb45..96a36dc738 100644 --- a/modules/users/tests/client/edit-profile.client.controller.tests.js +++ b/modules/users/tests/client/edit-profile.client.controller.tests.js @@ -62,12 +62,18 @@ roles: ['user'] }; + $httpBackend.whenGET('api/users/me').respond(Authentication.user); + // Initialize the Articles controller. EditProfileController = $controller('EditProfileController as vm', { $scope: $scope }); })); + afterEach(inject(function (Authentication) { + Authentication.signout(); + })); + describe('Update User Profile', function () { it('should have user context', inject(function (UsersService) { diff --git a/modules/users/tests/client/password-validator.client.directive.tests.js b/modules/users/tests/client/password-validator.client.directive.tests.js index 3e3f4e8f5a..87b28bf6b0 100644 --- a/modules/users/tests/client/password-validator.client.directive.tests.js +++ b/modules/users/tests/client/password-validator.client.directive.tests.js @@ -20,7 +20,6 @@ $httpBackend = _$httpBackend_; $httpBackend.whenGET('api/users/me').respond({}); - $httpBackend.flush(); scope.passwordMock = { password: 'P@ssw0rd!!' diff --git a/modules/users/tests/client/password-verify.client.directive.tests.js b/modules/users/tests/client/password-verify.client.directive.tests.js index b84d414b31..21d12aee1b 100644 --- a/modules/users/tests/client/password-verify.client.directive.tests.js +++ b/modules/users/tests/client/password-verify.client.directive.tests.js @@ -20,7 +20,6 @@ $httpBackend = _$httpBackend_; $httpBackend.whenGET('api/users/me').respond({}); - $httpBackend.flush(); scope.passwordMock = { newPassword: 'P@ssw0rd!!', diff --git a/modules/users/tests/client/password.client.controller.tests.js b/modules/users/tests/client/password.client.controller.tests.js index 9c955b5644..9fed71a0f6 100644 --- a/modules/users/tests/client/password.client.controller.tests.js +++ b/modules/users/tests/client/password.client.controller.tests.js @@ -45,7 +45,6 @@ // Ignore parent template gets on state transition $httpBackend.whenGET('/modules/core/client/views/404.client.view.html').respond(200); $httpBackend.whenGET('api/users/me').respond({ user: { username: 'test', roles: ['user'] } }); - $httpBackend.flush(); // Mock logged in user Authentication.user = { From 72ad45b9f0767b2ef85cf5de4b59bf3acd533628 Mon Sep 17 00:00:00 2001 From: mleanos Date: Tue, 6 Sep 2016 13:06:27 -0700 Subject: [PATCH 09/16] Missing dependency after merge & update test Added missing dependencies after manual merge conflict resolution. Also, updated test to include JWT auth header. --- modules/users/tests/server/user.server.routes.tests.js | 1 + package.json | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/users/tests/server/user.server.routes.tests.js b/modules/users/tests/server/user.server.routes.tests.js index 2e1a6acf2d..b0949f1ee7 100644 --- a/modules/users/tests/server/user.server.routes.tests.js +++ b/modules/users/tests/server/user.server.routes.tests.js @@ -889,6 +889,7 @@ describe('User CRUD tests', function () { // Get own user details agent.put('/api/users') + .set('Authorization', 'JWT ' + signinRes.body.token) .send(userUpdate) .expect(200) .end(function (err, res) { diff --git a/package.json b/package.json index e0dfba5016..d0ad5a78d9 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "glob": "~7.1.0", "helmet": "~2.3.0", "jasmine-core": "~2.5.0", + "jsonwebtoken": "~5.5.4", "lodash": "~4.16.2", "lusca": "~1.4.1", "method-override": "~2.3.5", @@ -72,6 +73,7 @@ "phantomjs-prebuilt": "~2.1.4", "serve-favicon": "~2.3.0", "socket.io": "^1.4.8", + "socketio-auth": "0.0.5", "validator": "~6.0.0", "winston": "^2.2.0", "wiredep": "~4.0.0" From 692d183d1fa157c584844aad59bba1de74a3fd1c Mon Sep 17 00:00:00 2001 From: mleanos Date: Tue, 6 Sep 2016 16:21:54 -0700 Subject: [PATCH 10/16] Add back passport serialization Added back the passport serialization callbacks, due to an error with the multer uploads. --- .../users/server/config/users.server.config.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/users/server/config/users.server.config.js b/modules/users/server/config/users.server.config.js index 0f5811bc45..d0619a6682 100644 --- a/modules/users/server/config/users.server.config.js +++ b/modules/users/server/config/users.server.config.js @@ -12,6 +12,21 @@ var passport = require('passport'), * Module init function */ module.exports = function (app, db) { + + // Serialize sessions + passport.serializeUser(function (user, done) { + done(null, user.id); + }); + + // Deserialize sessions + passport.deserializeUser(function (id, done) { + User.findOne({ + _id: id + }, '-salt -password', function (err, user) { + done(err, user); + }); + }); + // Initialize strategies config.utils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function (strategy) { require(path.resolve(strategy))(config); From 1123f2f9730b73e42a8f71efba44a362e2aad6ad Mon Sep 17 00:00:00 2001 From: mleanos Date: Tue, 6 Sep 2016 19:25:10 -0700 Subject: [PATCH 11/16] Passport serialization & Profile Image upload The user's profile image upload was throwing an error stating that the "user wasn't serialized". This was caused by the attempt to "login" using the req.login which uses Passport & expects the User to be serialized with the request. The fix was to remove the `req.login` attempt, since there shouldn't be a need to log the user in again (not even sure why that was happening in the first place). As a result, the Passport serialization in the User server configuration is no longer needed. This also removes that functionality. --- .../users/server/config/users.server.config.js | 15 --------------- .../users/users.profile.server.controller.js | 13 ------------- 2 files changed, 28 deletions(-) diff --git a/modules/users/server/config/users.server.config.js b/modules/users/server/config/users.server.config.js index d0619a6682..0f5811bc45 100644 --- a/modules/users/server/config/users.server.config.js +++ b/modules/users/server/config/users.server.config.js @@ -12,21 +12,6 @@ var passport = require('passport'), * Module init function */ module.exports = function (app, db) { - - // Serialize sessions - passport.serializeUser(function (user, done) { - done(null, user.id); - }); - - // Deserialize sessions - passport.deserializeUser(function (id, done) { - User.findOne({ - _id: id - }, '-salt -password', function (err, user) { - done(err, user); - }); - }); - // Initialize strategies config.utils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function (strategy) { require(path.resolve(strategy))(config); diff --git a/modules/users/server/controllers/users/users.profile.server.controller.js b/modules/users/server/controllers/users/users.profile.server.controller.js index 103ece7d9a..51d37f4948 100644 --- a/modules/users/server/controllers/users/users.profile.server.controller.js +++ b/modules/users/server/controllers/users/users.profile.server.controller.js @@ -62,7 +62,6 @@ exports.changeProfilePicture = function (req, res) { uploadImage() .then(updateUser) .then(deleteOldImage) - .then(login) .then(function () { res.json(user); }) @@ -118,18 +117,6 @@ exports.changeProfilePicture = function (req, res) { } }); } - - function login () { - return new Promise(function (resolve, reject) { - req.login(user, function (err) { - if (err) { - res.status(400).send(err); - } else { - resolve(); - } - }); - }); - } }; /** From 140974fb2ec989b8e10469f781c46296d4ffa07b Mon Sep 17 00:00:00 2001 From: mleanos Date: Tue, 6 Sep 2016 19:42:47 -0700 Subject: [PATCH 12/16] Authorization middleware Moved the express middleware for authorizing the User (on each request) to the new Authorization service. --- config/lib/authorization.js | 20 ++++++++++++++++++-- config/lib/express.js | 18 +++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/config/lib/authorization.js b/config/lib/authorization.js index 39d1ce03b9..d97f499ba7 100644 --- a/config/lib/authorization.js +++ b/config/lib/authorization.js @@ -2,15 +2,31 @@ var config = require('../config'), jwt = require('jsonwebtoken'), - lodash = require('lodash'); + lodash = require('lodash'), + passport = require('passport'); var auth = { - signToken: signToken + signToken: signToken, + authorize: authorizeRequest }; // Export the token auth service module.exports = auth; +function authorizeRequest(req, res, next) { + passport.authenticate('jwt', { session: false }, function (err, user) { + if (err) { + return next(new Error(err)); + } + + if (user) { + req.user = user; + } + + next(); + })(req, res, next); +} + // Sign the Token function signToken(user, options) { var payload, diff --git a/config/lib/express.js b/config/lib/express.js index 639fc854b1..bcd322dbe7 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -20,7 +20,7 @@ var config = require('../config'), path = require('path'), _ = require('lodash'), lusca = require('lusca'), - passport = require('passport'); + authorization = require('./authorization'); /** * Initialize local variables @@ -91,20 +91,8 @@ module.exports.initMiddleware = function (app) { app.use(cookieParser()); app.use(flash()); - // Authorize JWT - app.use(function (req, res, next) { - passport.authenticate('jwt', { session: false }, function (err, user) { - if (err) { - return next(new Error(err)); - } - - if (user) { - req.user = user; - } - - next(); - })(req, res, next); - }); + // Authorize Request + app.use(authorization.authorize); }; /** From ee60df46e7fb1917707900b6ec385dc746738aa5 Mon Sep 17 00:00:00 2001 From: mleanos Date: Mon, 16 Jan 2017 18:00:55 -0800 Subject: [PATCH 13/16] fix(tests): Broken tests & merge issues Fixes merge issues, after rebasing from upstream. Fixes broken tests that weren't setup to handle new JWT authentication logic. --- config/env/default.js | 7 ++++++- .../tests/client/admin.articles.client.controller.tests.js | 5 +++++ .../client/admin.list.articles.client.controller.tests.js | 2 +- modules/users/client/services/users.client.service.js | 4 ++++ .../users/users.authentication.server.controller.js | 3 +-- .../tests/client/authentication.client.controller.tests.js | 6 +++--- .../users/tests/client/password.client.controller.tests.js | 2 +- modules/users/tests/server/user.server.routes.tests.js | 4 +++- 8 files changed, 24 insertions(+), 9 deletions(-) diff --git a/config/env/default.js b/config/env/default.js index 35d1948bd2..d154797ff6 100644 --- a/config/env/default.js +++ b/config/env/default.js @@ -63,6 +63,11 @@ module.exports = { minPhraseLength: 20, minOptionalTestsToPass: 4 } + }, + jwt: { + secret: process.env.JWT_SECRET || 'M3@N_R0CK5', + options: { + expiresIn: process.env.JWT_EXPIRES_IN || '1d' + } } - }; diff --git a/modules/articles/tests/client/admin.articles.client.controller.tests.js b/modules/articles/tests/client/admin.articles.client.controller.tests.js index 32964a8fe3..682b13b80e 100644 --- a/modules/articles/tests/client/admin.articles.client.controller.tests.js +++ b/modules/articles/tests/client/admin.articles.client.controller.tests.js @@ -91,6 +91,7 @@ it('should send a POST request with the form input values and then locate to new object URL', inject(function (ArticlesService) { // Set POST response $httpBackend.expectPOST('/api/articles', sampleArticlePostData).respond(mockArticle); + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); // Run controller functionality $scope.vm.save(true); @@ -107,6 +108,7 @@ $httpBackend.expectPOST('/api/articles', sampleArticlePostData).respond(400, { message: errorMessage }); + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); $scope.vm.save(true); $httpBackend.flush(); @@ -124,6 +126,7 @@ it('should update a valid article', inject(function (ArticlesService) { // Set PUT response $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); // Run controller functionality $scope.vm.save(true); @@ -140,6 +143,7 @@ $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, { message: errorMessage }); + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); $scope.vm.save(true); $httpBackend.flush(); @@ -159,6 +163,7 @@ spyOn(window, 'confirm').and.returnValue(true); $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); $scope.vm.remove(); $httpBackend.flush(); diff --git a/modules/articles/tests/client/admin.list.articles.client.controller.tests.js b/modules/articles/tests/client/admin.list.articles.client.controller.tests.js index 90bd19f1a6..5526785f23 100644 --- a/modules/articles/tests/client/admin.list.articles.client.controller.tests.js +++ b/modules/articles/tests/client/admin.list.articles.client.controller.tests.js @@ -81,7 +81,7 @@ it('should send a GET request and return all articles', inject(function (ArticlesService) { // Set POST response $httpBackend.expectGET('/api/articles').respond(mockArticleList); - + $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); $httpBackend.flush(); diff --git a/modules/users/client/services/users.client.service.js b/modules/users/client/services/users.client.service.js index dd51119448..8af26ce8ba 100644 --- a/modules/users/client/services/users.client.service.js +++ b/modules/users/client/services/users.client.service.js @@ -13,6 +13,10 @@ update: { method: 'PUT' }, + me: { + method: 'GET', + url: 'api/users/me' + }, updatePassword: { method: 'POST', url: '/api/users/password' diff --git a/modules/users/server/controllers/users/users.authentication.server.controller.js b/modules/users/server/controllers/users/users.authentication.server.controller.js index fe12d3ab9d..cb93338af9 100644 --- a/modules/users/server/controllers/users/users.authentication.server.controller.js +++ b/modules/users/server/controllers/users/users.authentication.server.controller.js @@ -49,7 +49,7 @@ exports.signup = function (req, res) { * Signin after passport authentication */ exports.signin = function (req, res, next) { - passport.authenticate('local', function (err, user) { + passport.authenticate('local', function (err, user, info) { if (err || !user) { res.status(422).send(info); } else { @@ -101,7 +101,6 @@ exports.oauthCallback = function (strategy) { var token = authorization.signToken(user); return res.redirect(info.redirect_to || '/'); - }); })(req, res, next); }; }; diff --git a/modules/users/tests/client/authentication.client.controller.tests.js b/modules/users/tests/client/authentication.client.controller.tests.js index a65d489d44..38bc349142 100644 --- a/modules/users/tests/client/authentication.client.controller.tests.js +++ b/modules/users/tests/client/authentication.client.controller.tests.js @@ -64,7 +64,7 @@ $templateCache.put('/modules/core/client/views/home.client.view.html', ''); // Test expected GET request - $httpBackend.when('POST', '/api/auth/signin').respond(200, { username: 'Fred' }); + $httpBackend.when('POST', '/api/auth/signin').respond(200, { user: { username: 'Fred' }, token: 'somejwttoken' }); scope.vm.signin(true); $httpBackend.flush(); @@ -77,7 +77,7 @@ it('should login with a correct email and password', inject(function ($templateCache) { $templateCache.put('/modules/core/client/views/home.client.view.html', ''); // Test expected GET request - $httpBackend.when('POST', '/api/auth/signin').respond(200, { email: 'Fred@email.com' }); + $httpBackend.when('POST', '/api/auth/signin').respond(200, { user: { email: 'Fred@email.com' }, token: 'somejwttoken' }); $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); scope.vm.signin(true); @@ -152,7 +152,7 @@ // Test expected GET request scope.vm.authentication.user = 'Fred'; - $httpBackend.when('POST', '/api/auth/signup').respond(200, { username: 'Fred' }); + $httpBackend.when('POST', '/api/auth/signup').respond(200, { user: { username: 'Fred' }, token: 'somejwttoken' }); $httpBackend.when('GET', 'api/users/me').respond(200, { user: 'Fred' }); scope.vm.signup(true); diff --git a/modules/users/tests/client/password.client.controller.tests.js b/modules/users/tests/client/password.client.controller.tests.js index 9fed71a0f6..fecdf79c2b 100644 --- a/modules/users/tests/client/password.client.controller.tests.js +++ b/modules/users/tests/client/password.client.controller.tests.js @@ -68,7 +68,7 @@ }); describe('Logged out user', function() { - beforeEach(inject(function($controller, $rootScope, _$window_, _$stateParams_, _$httpBackend_, _$location_, _Notification_) { + beforeEach(inject(function($controller, $rootScope, _$window_, _$stateParams_, _$httpBackend_, _$location_, _Notification_, _Authentication_) { // Set a new global scope scope = $rootScope.$new(); diff --git a/modules/users/tests/server/user.server.routes.tests.js b/modules/users/tests/server/user.server.routes.tests.js index b0949f1ee7..be1e8ccbed 100644 --- a/modules/users/tests/server/user.server.routes.tests.js +++ b/modules/users/tests/server/user.server.routes.tests.js @@ -1019,6 +1019,7 @@ describe('User CRUD tests', function () { } agent.post('/api/users/picture') + .set('Authorization', 'JWT ' + signinRes.body.token) .attach('newProfilePicture', './modules/users/tests/server/img/text-file.txt') .send(credentials) .expect(422) @@ -1032,13 +1033,14 @@ describe('User CRUD tests', function () { agent.post('/api/auth/signin') .send(credentials) .expect(200) - .end(function (signinErr) { + .end(function (signinErr, signinRes) { // Handle signin error if (signinErr) { return done(signinErr); } agent.post('/api/users/picture') + .set('Authorization', 'JWT ' + signinRes.body.token) .attach('newProfilePicture', './modules/users/tests/server/img/too-big-file.png') .send(credentials) .expect(422) From 661958f61e278b9885d84cee17ce8291c397568a Mon Sep 17 00:00:00 2001 From: mleanos Date: Mon, 16 Jan 2017 18:46:20 -0800 Subject: [PATCH 14/16] fix(user): Client Users Service me() method Fixes the client-side UsersService.me() api route, that was missing the leading "/". Also cleaned up the client-side Authentication's `refresh()` method by removing ununsed parameters. --- modules/users/client/services/authentication.client.service.js | 2 +- modules/users/client/services/users.client.service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/users/client/services/authentication.client.service.js b/modules/users/client/services/authentication.client.service.js index 0ced369661..711e5cd3ff 100644 --- a/modules/users/client/services/authentication.client.service.js +++ b/modules/users/client/services/authentication.client.service.js @@ -58,7 +58,7 @@ $state.go('home', { reload: true }); } - function refresh(requestFromServer, callback) { + function refresh() { readyPromise = $q.defer(); UsersService.me().$promise diff --git a/modules/users/client/services/users.client.service.js b/modules/users/client/services/users.client.service.js index 8af26ce8ba..0b839074a5 100644 --- a/modules/users/client/services/users.client.service.js +++ b/modules/users/client/services/users.client.service.js @@ -15,7 +15,7 @@ }, me: { method: 'GET', - url: 'api/users/me' + url: '/api/users/me' }, updatePassword: { method: 'POST', From f64140e0328dc0c31c2c90d34aabddcd750ee370 Mon Sep 17 00:00:00 2001 From: mleanos Date: Fri, 20 Jan 2017 11:22:38 -0800 Subject: [PATCH 15/16] feat(angular): Authentication ready promise Modified the client-side Authentication service's ready promise to ensure we can handle the resolve externally from the service. This is critical in the Angular route filter for changing state, in order to check that the user has sufficient permissions to access the client-side route. Fixes an issue with the redirect url not getting set properly after removing sessions. Fixes Notification message after user signs in, to correctly show the user's first name. Upgraded version of passport-jwt library. --- .../client/config/core.client.route-filter.js | 60 ++++++++++--------- .../authentication.client.controller.js | 2 +- .../services/authentication.client.service.js | 29 +++++---- modules/users/server/config/strategies/jwt.js | 5 +- .../users.authentication.server.controller.js | 14 +++-- package.json | 2 +- 6 files changed, 63 insertions(+), 49 deletions(-) diff --git a/modules/core/client/config/core.client.route-filter.js b/modules/core/client/config/core.client.route-filter.js index d56518c701..85f3b89214 100644 --- a/modules/core/client/config/core.client.route-filter.js +++ b/modules/core/client/config/core.client.route-filter.js @@ -5,41 +5,43 @@ .module('core') .run(routeFilter); - routeFilter.$inject = ['$rootScope', '$state', 'Authentication']; + routeFilter.$inject = ['$rootScope', '$state', 'Authentication', '$log']; - function routeFilter($rootScope, $state, Authentication) { - - Authentication.ready - .then(function (auth) { - $rootScope.$on('$stateChangeStart', stateChangeStart); - }); + function routeFilter($rootScope, $state, Authentication, $log) { + $rootScope.$on('$stateChangeStart', stateChangeStart); $rootScope.$on('$stateChangeSuccess', stateChangeSuccess); function stateChangeStart(event, toState, toParams, fromState, fromParams) { - // Check authentication before changing state - if (toState.data && toState.data.roles && toState.data.roles.length > 0) { - var allowed = false; - - for (var i = 0, roles = toState.data.roles; i < roles.length; i++) { - if ((roles[i] === 'guest') || (Authentication.user && Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(roles[i]) !== -1)) { - allowed = true; - break; - } - } - - if (!allowed) { - event.preventDefault(); - if (Authentication.user !== null && typeof Authentication.user === 'object') { - $state.transitionTo('forbidden'); - } else { - $state.go('authentication.signin').then(function () { - // Record previous state - storePreviousState(toState, toParams); - }); + Authentication.ready.promise + .then(function () { + // Check authentication before changing state + if (toState.data && toState.data.roles && toState.data.roles.length > 0) { + var allowed = false; + + for (var i = 0, roles = toState.data.roles; i < roles.length; i++) { + if ((roles[i] === 'guest') || (Authentication.user && Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(roles[i]) !== -1)) { + allowed = true; + break; + } + } + + if (!allowed) { + event.preventDefault(); + if (Authentication.user !== null && typeof Authentication.user === 'object') { + $state.transitionTo('forbidden'); + } else { + $state.go('authentication.signin').then(function () { + // Record previous state + storePreviousState(toState, toParams); + }); + } + } } - } - } + }) + .catch(function (errorResponse) { + $log.error(errorResponse.data); + }); } function stateChangeSuccess(event, toState, toParams, fromState, fromParams) { diff --git a/modules/users/client/controllers/authentication.client.controller.js b/modules/users/client/controllers/authentication.client.controller.js index 9d13f4aedd..29764ad108 100644 --- a/modules/users/client/controllers/authentication.client.controller.js +++ b/modules/users/client/controllers/authentication.client.controller.js @@ -80,7 +80,7 @@ function onUserSigninSuccess(response) { // If successful we login the user client-side using the JWT Token Authentication.login(response.user, response.token); - Notification.info({ message: 'Welcome ' + response.firstName }); + Notification.info({ message: 'Welcome ' + response.user.firstName }); // And redirect to the previous or home page $state.go($state.previous.state.name || 'home', $state.previous.params); } diff --git a/modules/users/client/services/authentication.client.service.js b/modules/users/client/services/authentication.client.service.js index 711e5cd3ff..904cbdd125 100644 --- a/modules/users/client/services/authentication.client.service.js +++ b/modules/users/client/services/authentication.client.service.js @@ -10,7 +10,6 @@ Authentication.$inject = ['$window', '$state', '$http', '$location', '$q', 'UsersService']; function Authentication($window, $state, $http, $location, $q, UsersService) { - var readyPromise = $q.defer(); var auth = { user: null, @@ -18,7 +17,7 @@ login: login, signout: signout, refresh: refresh, - ready: readyPromise.promise + ready: $q.defer() }; // Initialize service @@ -34,9 +33,10 @@ if (token) { auth.token = token; $http.defaults.headers.common.Authorization = 'JWT ' + token; + refresh(); } else { - readyPromise.resolve(auth); + auth.ready.resolve(); } } @@ -47,7 +47,7 @@ localStorage.setItem('token', token); $http.defaults.headers.common.Authorization = 'JWT ' + token; - readyPromise.resolve(auth); + auth.ready.resolve(); } function signout() { @@ -59,16 +59,19 @@ } function refresh() { - readyPromise = $q.defer(); - UsersService.me().$promise - .then(function (user) { - auth.user = user; - readyPromise.resolve(auth); - }) - .catch(function (errorResponse) { - readyPromise.reject(errorResponse); - }); + .then(function (user) { + if (!user || !user.roles || !user.roles.length) { + signout(); + return auth.ready.resolve(); + } + + auth.user = user; + auth.ready.resolve(); + }) + .catch(function (errorResponse) { + auth.ready.reject(errorResponse); + }); } } }()); diff --git a/modules/users/server/config/strategies/jwt.js b/modules/users/server/config/strategies/jwt.js index badf620afa..96093897d4 100644 --- a/modules/users/server/config/strategies/jwt.js +++ b/modules/users/server/config/strategies/jwt.js @@ -10,7 +10,10 @@ var passport = require('passport'), module.exports = function (config) { var opts = { - jwtFromRequest: ExtractJwt.fromAuthHeader(), + jwtFromRequest: ExtractJwt.fromExtractors([ + ExtractJwt.fromUrlQueryParameter('token'), + ExtractJwt.fromAuthHeader() + ]), secretOrKey: config.jwt.secret // opts.issuer = "accounts.examplesoft.com", // opts.audience = "yoursite.net" diff --git a/modules/users/server/controllers/users/users.authentication.server.controller.js b/modules/users/server/controllers/users/users.authentication.server.controller.js index cb93338af9..3c606eb8d0 100644 --- a/modules/users/server/controllers/users/users.authentication.server.controller.js +++ b/modules/users/server/controllers/users/users.authentication.server.controller.js @@ -77,7 +77,7 @@ exports.signout = function (req, res) { exports.oauthCall = function (strategy, scope) { return function (req, res, next) { if (req.query && req.query.redirect_to) - req.session.redirect_to = req.query.redirect_to; + req.redirect_to = req.query.redirect_to; // Authenticate passport.authenticate(strategy, scope)(req, res, next); @@ -100,7 +100,13 @@ exports.oauthCallback = function (strategy) { } var token = authorization.signToken(user); - return res.redirect(info.redirect_to || '/'); + var redirect = info.redirect_to || '/'; + + if (token) { + redirect += '?token=' + token; + } + + return res.redirect(redirect); })(req, res, next); }; }; @@ -114,8 +120,8 @@ exports.saveOAuthUserProfile = function (req, providerUserProfile, done) { // Set redirection path on session. // Do not redirect to a signin or signup page - if (noReturnUrls.indexOf(req.session.redirect_to) === -1) - info.redirect_to = req.session.redirect_to; + if (noReturnUrls.indexOf(req.redirect_to) === -1) + info.redirect_to = req.redirect_to; if (!req.user) { // Define a search query fields diff --git a/package.json b/package.json index d0ad5a78d9..0cf89f803c 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "passport-facebook": "~2.1.0", "passport-github": "~1.1.0", "passport-google-oauth": "~1.0.0", - "passport-jwt": "~2.0.0", + "passport-jwt": "~2.2.1", "passport-linkedin": "~1.0.0", "passport-local": "~1.0.0", "passport-paypal-openidconnect": "~0.1.1", From c7a6ea8782008ed3018cd665e6465ce84a615433 Mon Sep 17 00:00:00 2001 From: mleanos Date: Fri, 20 Jan 2017 11:55:43 -0800 Subject: [PATCH 16/16] fix(tests): Broken client-side tests Fixes broken client-side tests after changing the url reference of the /api/users/me endpoint, in the UsersService. --- .../client/admin.articles.client.controller.tests.js | 10 +++++----- .../admin.list.articles.client.controller.tests.js | 2 +- .../tests/client/articles.client.controller.tests.js | 2 +- .../client/authentication.client.controller.tests.js | 10 +++++----- .../client/edit-profile.client.controller.tests.js | 2 +- .../password-validator.client.directive.tests.js | 2 +- .../client/password-verify.client.directive.tests.js | 2 +- .../tests/client/password.client.controller.tests.js | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/articles/tests/client/admin.articles.client.controller.tests.js b/modules/articles/tests/client/admin.articles.client.controller.tests.js index 682b13b80e..04eee9ae52 100644 --- a/modules/articles/tests/client/admin.articles.client.controller.tests.js +++ b/modules/articles/tests/client/admin.articles.client.controller.tests.js @@ -91,7 +91,7 @@ it('should send a POST request with the form input values and then locate to new object URL', inject(function (ArticlesService) { // Set POST response $httpBackend.expectPOST('/api/articles', sampleArticlePostData).respond(mockArticle); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); // Run controller functionality $scope.vm.save(true); @@ -108,7 +108,7 @@ $httpBackend.expectPOST('/api/articles', sampleArticlePostData).respond(400, { message: errorMessage }); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); $scope.vm.save(true); $httpBackend.flush(); @@ -126,7 +126,7 @@ it('should update a valid article', inject(function (ArticlesService) { // Set PUT response $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); // Run controller functionality $scope.vm.save(true); @@ -143,7 +143,7 @@ $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, { message: errorMessage }); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); $scope.vm.save(true); $httpBackend.flush(); @@ -163,7 +163,7 @@ spyOn(window, 'confirm').and.returnValue(true); $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); $scope.vm.remove(); $httpBackend.flush(); diff --git a/modules/articles/tests/client/admin.list.articles.client.controller.tests.js b/modules/articles/tests/client/admin.list.articles.client.controller.tests.js index 5526785f23..16cdf66b43 100644 --- a/modules/articles/tests/client/admin.list.articles.client.controller.tests.js +++ b/modules/articles/tests/client/admin.list.articles.client.controller.tests.js @@ -81,7 +81,7 @@ it('should send a GET request and return all articles', inject(function (ArticlesService) { // Set POST response $httpBackend.expectGET('/api/articles').respond(mockArticleList); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); $httpBackend.flush(); diff --git a/modules/articles/tests/client/articles.client.controller.tests.js b/modules/articles/tests/client/articles.client.controller.tests.js index 7e54e86517..aacd083bd3 100644 --- a/modules/articles/tests/client/articles.client.controller.tests.js +++ b/modules/articles/tests/client/articles.client.controller.tests.js @@ -46,7 +46,7 @@ Authentication = _Authentication_; ArticlesService = _ArticlesService_; - $httpBackend.whenGET('api/users/me').respond({}); + $httpBackend.whenGET('/api/users/me').respond({}); // create mock article mockArticle = new ArticlesService({ diff --git a/modules/users/tests/client/authentication.client.controller.tests.js b/modules/users/tests/client/authentication.client.controller.tests.js index 38bc349142..e3fc18337b 100644 --- a/modules/users/tests/client/authentication.client.controller.tests.js +++ b/modules/users/tests/client/authentication.client.controller.tests.js @@ -51,7 +51,7 @@ $httpBackend.whenGET('/modules/core/client/views/home.client.view.html').respond(200); $httpBackend.whenGET('/modules/core/client/views/400.client.view.html').respond(200); - $httpBackend.when('GET', 'api/users/me').respond(200, {}); + $httpBackend.when('GET', '/api/users/me').respond(200, {}); // Initialize the Authentication controller AuthenticationController = $controller('AuthenticationController as vm', { @@ -78,7 +78,7 @@ $templateCache.put('/modules/core/client/views/home.client.view.html', ''); // Test expected GET request $httpBackend.when('POST', '/api/auth/signin').respond(200, { user: { email: 'Fred@email.com' }, token: 'somejwttoken' }); - $httpBackend.when('GET', 'api/users/me').respond(200, 'Fred'); + $httpBackend.when('GET', '/api/users/me').respond(200, 'Fred'); scope.vm.signin(true); $httpBackend.flush(); @@ -104,7 +104,7 @@ // Test expected GET request $httpBackend.when('POST', '/api/auth/signin').respond(200, { user: 'Fred' }); - $httpBackend.when('GET', 'api/users/me').respond(200, { user: 'Fred' }); + $httpBackend.when('GET', '/api/users/me').respond(200, { user: 'Fred' }); scope.vm.signin(true); $httpBackend.flush(); @@ -153,7 +153,7 @@ // Test expected GET request scope.vm.authentication.user = 'Fred'; $httpBackend.when('POST', '/api/auth/signup').respond(200, { user: { username: 'Fred' }, token: 'somejwttoken' }); - $httpBackend.when('GET', 'api/users/me').respond(200, { user: 'Fred' }); + $httpBackend.when('GET', '/api/users/me').respond(200, { user: 'Fred' }); scope.vm.signup(true); $httpBackend.flush(); @@ -186,7 +186,7 @@ $location = _$location_; $location.path = jasmine.createSpy().and.returnValue(true); - $httpBackend.when('GET', 'api/users/me').respond(200, {}); + $httpBackend.when('GET', '/api/users/me').respond(200, {}); // Mock logged in user _Authentication_.user = { diff --git a/modules/users/tests/client/edit-profile.client.controller.tests.js b/modules/users/tests/client/edit-profile.client.controller.tests.js index 96a36dc738..cd516e0ddb 100644 --- a/modules/users/tests/client/edit-profile.client.controller.tests.js +++ b/modules/users/tests/client/edit-profile.client.controller.tests.js @@ -62,7 +62,7 @@ roles: ['user'] }; - $httpBackend.whenGET('api/users/me').respond(Authentication.user); + $httpBackend.whenGET('/api/users/me').respond(Authentication.user); // Initialize the Articles controller. EditProfileController = $controller('EditProfileController as vm', { diff --git a/modules/users/tests/client/password-validator.client.directive.tests.js b/modules/users/tests/client/password-validator.client.directive.tests.js index 87b28bf6b0..65cb0a2cfd 100644 --- a/modules/users/tests/client/password-validator.client.directive.tests.js +++ b/modules/users/tests/client/password-validator.client.directive.tests.js @@ -19,7 +19,7 @@ $compile = _$compile_; $httpBackend = _$httpBackend_; - $httpBackend.whenGET('api/users/me').respond({}); + $httpBackend.whenGET('/api/users/me').respond({}); scope.passwordMock = { password: 'P@ssw0rd!!' diff --git a/modules/users/tests/client/password-verify.client.directive.tests.js b/modules/users/tests/client/password-verify.client.directive.tests.js index 21d12aee1b..046abe07c6 100644 --- a/modules/users/tests/client/password-verify.client.directive.tests.js +++ b/modules/users/tests/client/password-verify.client.directive.tests.js @@ -19,7 +19,7 @@ $compile = _$compile_; $httpBackend = _$httpBackend_; - $httpBackend.whenGET('api/users/me').respond({}); + $httpBackend.whenGET('/api/users/me').respond({}); scope.passwordMock = { newPassword: 'P@ssw0rd!!', diff --git a/modules/users/tests/client/password.client.controller.tests.js b/modules/users/tests/client/password.client.controller.tests.js index fecdf79c2b..42d7067843 100644 --- a/modules/users/tests/client/password.client.controller.tests.js +++ b/modules/users/tests/client/password.client.controller.tests.js @@ -44,7 +44,7 @@ // Ignore parent template gets on state transition $httpBackend.whenGET('/modules/core/client/views/404.client.view.html').respond(200); - $httpBackend.whenGET('api/users/me').respond({ user: { username: 'test', roles: ['user'] } }); + $httpBackend.whenGET('/api/users/me').respond({ user: { username: 'test', roles: ['user'] } }); // Mock logged in user Authentication.user = { @@ -87,7 +87,7 @@ Authentication.user = null; - $httpBackend.whenGET('api/users/me').respond({ user: null }); + $httpBackend.whenGET('/api/users/me').respond({ user: null }); // Ignore parent template gets on state transition $httpBackend.whenGET('/modules/core/client/views/404.client.view.html').respond(200);