From d940edaf25d16450e986496185bdbf53a6f768e5 Mon Sep 17 00:00:00 2001 From: Florian Blouet Date: Fri, 14 Oct 2016 15:11:34 +0200 Subject: [PATCH] Add JWT strategy for authentification strategy can be toggled between basic and jwt auth. --- .env-sample | 2 ++ config.js | 6 ++++ index.js | 1 - manifest.js | 3 ++ package.json | 2 ++ server.js | 1 - server/api/accounts.js | 23 +++++++------ server/api/admin-groups.js | 13 +++---- server/api/admins.js | 19 ++++++----- server/api/auth-attempts.js | 7 ++-- server/api/login.js | 14 ++++++-- server/api/logout.js | 3 +- server/api/sessions.js | 7 ++-- server/api/signup.js | 2 ++ server/api/statuses.js | 11 +++--- server/api/users.js | 19 ++++++----- server/auth.js | 67 +++++++++++++++++++++++++++++++++++-- server/models/session.js | 2 -- server/utils/token.js | 22 ++++++++++++ 19 files changed, 167 insertions(+), 57 deletions(-) create mode 100644 server/utils/token.js diff --git a/.env-sample b/.env-sample index 49e0789..c6f4939 100644 --- a/.env-sample +++ b/.env-sample @@ -1 +1,3 @@ SMTP_PASSWORD=secret +COOKIE_SECRET=secret +JWT_SECRET=secret \ No newline at end of file diff --git a/config.js b/config.js index c5950cc..6b51e27 100644 --- a/config.js +++ b/config.js @@ -21,6 +21,7 @@ const config = { $default: 9000 } }, + authStrategy: 'jwt', authAttempts: { forIp: 50, forIpAndUser: 7 @@ -30,6 +31,11 @@ const config = { production: process.env.COOKIE_SECRET, $default: '!k3yb04rdK4tz~4qu4~k3yb04rdd0gz!' }, + jwtSecret: { + $filter: 'env', + production: process.env.JWT_SECRET, + $default: 'aStrongJwtSecret-#mgtfYK@QuRV8VMM7T>WfN4;^fMVr)y' + }, hapiMongoModels: { mongodb: { uri: { diff --git a/index.js b/index.js index 82d42c5..8871c49 100644 --- a/index.js +++ b/index.js @@ -7,5 +7,4 @@ const composeOptions = { relativeTo: __dirname }; - module.exports = Glue.compose.bind(Glue, Manifest.get('/'), composeOptions); diff --git a/manifest.js b/manifest.js index c4c752b..ab47e19 100644 --- a/manifest.js +++ b/manifest.js @@ -25,6 +25,9 @@ const manifest = { labels: ['web'] }], registrations: [ + { + plugin: 'hapi-auth-jwt2' + }, { plugin: 'hapi-auth-basic' }, diff --git a/package.json b/package.json index d2f6d68..c04a5d1 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,13 @@ "handlebars": "4.x.x", "hapi": "15.x.x", "hapi-auth-basic": "4.x.x", + "hapi-auth-jwt2": "^7.1.3", "hapi-mongo-models": "6.x.x", "hoek": "4.x.x", "inert": "4.x.x", "jade": "1.x.x", "joi": "9.x.x", + "jsonwebtoken": "^7.1.9", "lout": "9.x.x", "mongo-models": "1.x.x", "mongodb": "2.x.x", diff --git a/server.js b/server.js index 98b4174..6e58869 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,6 @@ Composer((err, server) => { } server.start(() => { - console.log('Started the plot device on port ' + server.info.port); }); }); diff --git a/server/api/accounts.js b/server/api/accounts.js index 22f6b95..fc5f4f1 100644 --- a/server/api/accounts.js +++ b/server/api/accounts.js @@ -3,6 +3,7 @@ const Async = require('async'); const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -20,7 +21,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -57,7 +58,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' } }, @@ -84,7 +85,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/my', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'account' } }, @@ -114,7 +115,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -144,7 +145,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -187,7 +188,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/my', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'account' }, validate: { @@ -229,7 +230,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}/user', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -340,7 +341,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}/user', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [{ @@ -425,7 +426,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}/notes', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -467,7 +468,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}/status', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -528,7 +529,7 @@ internals.applyRoutes = function (server, next) { path: '/accounts/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ diff --git a/server/api/admin-groups.js b/server/api/admin-groups.js index d95a77c..673fe59 100644 --- a/server/api/admin-groups.js +++ b/server/api/admin-groups.js @@ -2,6 +2,7 @@ const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -17,7 +18,7 @@ internals.applyRoutes = function (server, next) { path: '/admin-groups', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -57,7 +58,7 @@ internals.applyRoutes = function (server, next) { path: '/admin-groups/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ @@ -87,7 +88,7 @@ internals.applyRoutes = function (server, next) { path: '/admin-groups', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -120,7 +121,7 @@ internals.applyRoutes = function (server, next) { path: '/admin-groups/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -165,7 +166,7 @@ internals.applyRoutes = function (server, next) { path: '/admin-groups/{id}/permissions', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -206,7 +207,7 @@ internals.applyRoutes = function (server, next) { path: '/admin-groups/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { diff --git a/server/api/admins.js b/server/api/admins.js index c8e7fcc..e42724d 100644 --- a/server/api/admins.js +++ b/server/api/admins.js @@ -3,6 +3,7 @@ const Async = require('async'); const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -19,7 +20,7 @@ internals.applyRoutes = function (server, next) { path: '/admins', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -59,7 +60,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ @@ -89,7 +90,7 @@ internals.applyRoutes = function (server, next) { path: '/admins', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -122,7 +123,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -171,7 +172,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}/permissions', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -212,7 +213,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}/groups', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -253,7 +254,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}/user', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -370,7 +371,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}/user', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -463,7 +464,7 @@ internals.applyRoutes = function (server, next) { path: '/admins/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ diff --git a/server/api/auth-attempts.js b/server/api/auth-attempts.js index da9373f..3265cdd 100644 --- a/server/api/auth-attempts.js +++ b/server/api/auth-attempts.js @@ -2,6 +2,7 @@ const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -17,7 +18,7 @@ internals.applyRoutes = function (server, next) { path: '/auth-attempts', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -57,7 +58,7 @@ internals.applyRoutes = function (server, next) { path: '/auth-attempts/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ @@ -87,7 +88,7 @@ internals.applyRoutes = function (server, next) { path: '/auth-attempts/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ diff --git a/server/api/login.js b/server/api/login.js index c11a4ae..153ff98 100644 --- a/server/api/login.js +++ b/server/api/login.js @@ -4,7 +4,7 @@ const Bcrypt = require('bcrypt'); const Boom = require('boom'); const Config = require('../../config'); const Joi = require('joi'); - +const createToken = require('../utils/token'); const internals = {}; @@ -98,9 +98,16 @@ internals.applyRoutes = function (server, next) { }] }, handler: function (request, reply) { - const credentials = request.pre.session._id.toString() + ':' + request.pre.session.key; - const authHeader = 'Basic ' + new Buffer(credentials).toString('base64'); + const token = createToken(request.pre.user, request.pre.session); + + let authHeader = ''; + + if( Config.get('authStrategy') === 'simple') { + authHeader = 'Basic ' + new Buffer(credentials).toString('base64'); + } else { + authHeader = 'Bearer ' + token; + } reply({ user: { @@ -109,6 +116,7 @@ internals.applyRoutes = function (server, next) { email: request.pre.user.email, roles: request.pre.user.roles }, + id_token: token, session: request.pre.session, authHeader }); diff --git a/server/api/logout.js b/server/api/logout.js index 72c265b..1557757 100644 --- a/server/api/logout.js +++ b/server/api/logout.js @@ -1,5 +1,6 @@ 'use strict'; const Boom = require('boom'); +const Config = require('../../config'); const internals = {}; @@ -16,7 +17,7 @@ internals.applyRoutes = function (server, next) { config: { auth: { mode: 'try', - strategy: 'simple' + strategy: Config.get('/authStrategy') } }, handler: function (request, reply) { diff --git a/server/api/sessions.js b/server/api/sessions.js index 937d448..92ae840 100644 --- a/server/api/sessions.js +++ b/server/api/sessions.js @@ -2,6 +2,7 @@ const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -17,7 +18,7 @@ internals.applyRoutes = function (server, next) { path: '/sessions', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -57,7 +58,7 @@ internals.applyRoutes = function (server, next) { path: '/sessions/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ @@ -87,7 +88,7 @@ internals.applyRoutes = function (server, next) { path: '/sessions/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ diff --git a/server/api/signup.js b/server/api/signup.js index 7be2bcc..0927bd3 100644 --- a/server/api/signup.js +++ b/server/api/signup.js @@ -3,6 +3,7 @@ const Async = require('async'); const Boom = require('boom'); const Config = require('../../config'); const Joi = require('joi'); +const createToken = require('../utils/token'); const internals = {}; @@ -162,6 +163,7 @@ internals.applyRoutes = function (server, next) { roles: user.roles }, session: results.session, + id_token: createToken(user, results.session), authHeader }); }); diff --git a/server/api/statuses.js b/server/api/statuses.js index f9d48a7..63412f2 100644 --- a/server/api/statuses.js +++ b/server/api/statuses.js @@ -2,6 +2,7 @@ const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -17,7 +18,7 @@ internals.applyRoutes = function (server, next) { path: '/statuses', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -57,7 +58,7 @@ internals.applyRoutes = function (server, next) { path: '/statuses/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ @@ -87,7 +88,7 @@ internals.applyRoutes = function (server, next) { path: '/statuses', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -122,7 +123,7 @@ internals.applyRoutes = function (server, next) { path: '/statuses/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -164,7 +165,7 @@ internals.applyRoutes = function (server, next) { path: '/statuses/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ diff --git a/server/api/users.js b/server/api/users.js index 4a6d423..31b115f 100644 --- a/server/api/users.js +++ b/server/api/users.js @@ -2,6 +2,7 @@ const AuthPlugin = require('../auth'); const Boom = require('boom'); const Joi = require('joi'); +const Config = require('../../config'); const internals = {}; @@ -17,7 +18,7 @@ internals.applyRoutes = function (server, next) { path: '/users', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -69,7 +70,7 @@ internals.applyRoutes = function (server, next) { path: '/users/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, pre: [ @@ -99,7 +100,7 @@ internals.applyRoutes = function (server, next) { path: '/users/my', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: ['admin', 'account'] } }, @@ -129,7 +130,7 @@ internals.applyRoutes = function (server, next) { path: '/users', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -209,7 +210,7 @@ internals.applyRoutes = function (server, next) { path: '/users/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -303,7 +304,7 @@ internals.applyRoutes = function (server, next) { path: '/users/my', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: ['admin', 'account'] }, validate: { @@ -391,7 +392,7 @@ internals.applyRoutes = function (server, next) { path: '/users/{id}/password', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { @@ -446,7 +447,7 @@ internals.applyRoutes = function (server, next) { path: '/users/my/password', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: ['admin', 'account'] }, validate: { @@ -501,7 +502,7 @@ internals.applyRoutes = function (server, next) { path: '/users/{id}', config: { auth: { - strategy: 'simple', + strategy: Config.get('/authStrategy'), scope: 'admin' }, validate: { diff --git a/server/auth.js b/server/auth.js index b6b1060..3b5e048 100644 --- a/server/auth.js +++ b/server/auth.js @@ -1,7 +1,7 @@ 'use strict'; const Async = require('async'); const Boom = require('boom'); - +const Config = require('../config'); const internals = {}; @@ -16,7 +16,6 @@ internals.applyStrategy = function (server, next) { Async.auto({ session: function (done) { - Session.findByCredentials(username, password, done); }, user: ['session', function (results, done) { @@ -63,6 +62,64 @@ internals.applyStrategy = function (server, next) { }; +internals.applyJwtStrategy = function (server, next) { + const Session = server.plugins['hapi-mongo-models'].Session; + const User = server.plugins['hapi-mongo-models'].User; + + + server.auth.strategy('jwt', 'jwt', { + key: Config.get('/jwtSecret'), + verifyOptions: { algorithms: ['HS256'] }, + + validateFunc: function (decodedToken, request, callback) { + + Async.auto({ + session: function (done) { + Session.findByCredentials(decodedToken.sessionId, decodedToken.sessionKey, done); + }, + user: ['session', function (results, done) { + + if (!results.session) { + return done(); + } + + User.findById(results.session.userId, done); + }], + roles: ['user', function (results, done) { + + if (!results.user) { + return done(); + } + + results.user.hydrateRoles(done); + }], + scope: ['user', function (results, done) { + + if (!results.user || !results.user.roles) { + return done(); + } + + done(null, Object.keys(results.user.roles)); + }] + }, (err, results) => { + + if (err) { + return callback(err); + } + + if (!results.session) { + return callback(null, false); + } + + callback(null, Boolean(results.user), results); + }); + } + }); + + + next(); +}; + internals.preware = { ensureNotRoot: { assign: 'ensureNotRoot', @@ -105,7 +162,11 @@ internals.preware = { exports.register = function (server, options, next) { - server.dependency('hapi-mongo-models', internals.applyStrategy); + if( Config.get('/authStrategy') === 'simple') { + server.dependency('hapi-mongo-models', internals.applyStrategy); + } else { + server.dependency(['hapi-mongo-models', 'hapi-auth-jwt2'], internals.applyJwtStrategy); + } next(); }; diff --git a/server/models/session.js b/server/models/session.js index 042466b..635929c 100644 --- a/server/models/session.js +++ b/server/models/session.js @@ -46,7 +46,6 @@ class Session extends MongoModels { key: results.keyHash.hash, time: new Date() }; - self.insertOne(document, done); }], clean: ['newSession', function (results, done) { @@ -80,7 +79,6 @@ class Session extends MongoModels { self.findById(id, done); }, keyMatch: ['session', function (results, done) { - if (!results.session) { return done(null, false); } diff --git a/server/utils/token.js b/server/utils/token.js new file mode 100644 index 0000000..36b2071 --- /dev/null +++ b/server/utils/token.js @@ -0,0 +1,22 @@ +'use strict'; +const AuthPlugin = require('../auth'); +const jwt = require('jsonwebtoken'); +const Config = require('../../config'); + +function createToken(user, session) { + let scopes; + + // scopes to admin + if (AuthPlugin.preware.ensureAdminGroup('root')) { + scopes = 'admin'; + } + // Sign the JWT + return jwt.sign({ + id: user._id, + username: user.username, + sessionId: session._id.toString(), + sessionKey: session.key, + scope: scopes }, Config.get('/jwtSecret'), { algorithm: 'HS256', expiresIn: "1h" } ); +} + +module.exports = createToken; \ No newline at end of file