From b30989909509880f436b8d465408b378ca4cfabe Mon Sep 17 00:00:00 2001 From: Iain Collins Date: Mon, 13 Feb 2017 04:15:24 +0000 Subject: [PATCH] Refactored auth option handling with ES6 params This makes it much clearer what the method options are, what options are being passed and removes the amount of code required. Also improved error handling on startup. --- index.js | 10 ++- package.json | 4 +- routes/auth.js | 82 ++++++++++--------- .../{passport.js => passport-strategies.js} | 25 +++--- 4 files changed, 67 insertions(+), 54 deletions(-) rename routes/{passport.js => passport-strategies.js} (92%) diff --git a/index.js b/index.js index 1fd42e7..19ecdbf 100644 --- a/index.js +++ b/index.js @@ -75,8 +75,10 @@ app.prepare() }) .then(db => { // Once DB is available, setup sessions and routes for authentication - auth.configure(app, server, { - db: db, + auth.configure({ + app: app, + server: server, + user: db.models.user, secret: process.env.SESSION_SECRET }) @@ -106,3 +108,7 @@ app.prepare() console.log('> Ready on http://localhost:' + process.env.PORT + ' [' + process.env.NODE_ENV + ']') }) }) +.catch(err => { + console.log('An error occurred, unable to start the server') + console.log(err) +}) diff --git a/package.json b/package.json index 23209fc..127aa1b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nextjs-starter", - "version": "2.5.11", - "description": "A starter Next.js project", + "version": "2.5.13", + "description": "A starter Next.js project with email and oAuth authentication", "author": "Iain Collins ", "license": "ISC", "repository": "https://github.com/iaincollins/nextjs-starter.git", diff --git a/routes/auth.js b/routes/auth.js index ee8dbb0..366428c 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -17,48 +17,47 @@ const FileStore = require('session-file-store')(session) const nodemailer = require('nodemailer') const csrf = require('lusca').csrf() const uuid = require('uuid/v4') -const passport = require('./passport') - -exports.configure = (app, server, options) => { - if (!options) { - options = {} +const passportStrategies = require('./passport-strategies') + +exports.configure = ({ + app = null, // Next.js App + server = null, // Express Server + user: User = null, // User model + // URL base path for authentication routes + path = '/auth', + // Directory in ./pages/ where auth pages can be found + pages = 'auth', + // Secret used to encrypt session data on the server + secret = 'change-me', + // Sessions store for express-session (defaults to /tmp/sessions file store) + store = new FileStore({path: '/tmp/sessions', secret: secret}), + // Max session age in ms (default is 4 weeks) + // NB: With 'rolling: true' passed to session() the session expiry time will + // be reset every time a user visits the site again before it expires. + maxAge = 60000 * 60 * 24 * 7 * 4, + // How often the client should revalidate the session in ms (default 60s) + // Does not impact the session life on the server, but causes the client to + // always refetch session info after N seconds has elapsed since last + // checked. Sensible values are between 0 (always check the server) and a + // few minutes. + clientMaxAge = 60000, + // URL of the server (e.g. 'http://www.example.com'). Used when sending + // sign in links in emails. Autodetects to hostname if null. + serverUrl = null, + // Mailserver configuration for nodemailer (defaults to localhost if null) + mailserver = null + } = {}) => { + if (app === null) { + throw new Error('app option must be a next server instance') } - if (!options.db || !options.db.models || !options.db.models.user) { - throw new Error('Database with user model is a required option!') + if (server === null) { + throw new Error('server option must be an express server instance') } - const User = options.db.models.user - - // Base path for auth URLs - const path = options.path || '/auth' - - // Directory for auth pages - const pages = options.pages || 'auth' - - // The secret is used to encrypt/decrypt sessions (you should pass your own!) - const secret = options.secret || 'change-me' - - // Configure session store (defaults to using file system) - const store = options.store || new FileStore({path: '/tmp/sessions', secret: secret}) - - // Max session age in ms (default is 4 weeks) - // NB: With 'rolling: true' passed to session() the session expiry time will - // be reset every time a user visits the site again before it expires. - const maxAge = options.maxAge || 60000 * 60 * 24 * 7 * 4 - - // How often the client should revalidate the session in ms (default 60s) - // Does not impact the session life on the server, but causes the client to - // always refetch session info after N seconds has elapsed since last checked. - // Sensible values are between 0 (always check the server) and a few minutes. - const clientMaxAge = options.clientMaxAge || 60000 - - // URL of the server (e.g. 'http://www.example.com'). Used when sending - // sign-in emails. Autodetects current server hostname / domain if null. - const serverUrl = options.serverUrl || null - - // Mailserver (defaults to sending from localhost if null) - const mailserver = options.mailserver || null + if (User === null) { + throw new Error('user option must be a User model') + } // Load body parser to handle POST requests server.use(bodyParser.json()) @@ -84,7 +83,12 @@ exports.configure = (app, server, options) => { }) // With sessions connfigured (& before routes) we need to configure Passport - passport.configure(app, server, {db: options.db, path: path}) + // and trigger passport.initialize() before we add any routes + passportStrategies.configure({ + app: app, + server: server, + user: User + }) // Add route to get CSRF token via AJAX server.get(path + '/csrf', (req, res) => { diff --git a/routes/passport.js b/routes/passport-strategies.js similarity index 92% rename from routes/passport.js rename to routes/passport-strategies.js index fc7fe85..485acf9 100644 --- a/routes/passport.js +++ b/routes/passport-strategies.js @@ -1,23 +1,27 @@ /** - * Confgiure Passport Strategies + * Configure Passport Strategies */ 'use strict' const passport = require('passport') -exports.configure = (app, server, options) => { - if (!options) { - options = {} +exports.configure = ({ + app = null, // Next.js App + server = null, // Express Server + user: User = null, // User model + path = '/auth' // URL base path for authentication routes + } = {}) => { + if (app === null) { + throw new Error('app option must be a next server instance') } - if (!options.db || !options.db.models || !options.db.models.user) { - throw new Error('Database with user model is a required option!') + if (server === null) { + throw new Error('server option must be an express server instance') } - const User = options.db.models.user - - // Base path for auth URLs - const path = options.path || '/auth' + if (User === null) { + throw new Error('user option must be a User model') + } // Tell Passport how to seralize/deseralize user accounts passport.serializeUser(function (user, done) { @@ -154,7 +158,6 @@ exports.configure = (app, server, options) => { if (err) { return done(err) } - // If we already have an account associated with that email address in the databases, the user // should sign in with that account instead (to prevent them creating two accounts by mistake) // Note: Automatically linking them here could expose a potential security exploit allowing someone