diff --git a/README.md b/README.md index 5f32e166..41b2c84a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ -# @sensu/superlogin [![Build Status](https://travis-ci.org/sen-su/superlogin.png?branch=master)](https://travis-ci.org/sen-su/superlogin) +# @jaredthirsk/superlogin -## An up-to-date version of superlogin that is actually published. +A fork of @sensu/superlogin, which seems to be a strong contender as a current / maintained version of the original @colinskow/superlogin, (which seems to be broken and out of date.) + +Features I add to @sensu's version: + - options to disable + - authenticating to superlogin via API token (`authStrategies.userPassToken = false`) + - authenticating to superlogin via username and password (`authStrategies.userPass = false`) + +# Superlogin SuperLogin is a full-featured NodeJS/Express user authentication solution for APIs and Single Page Apps (SPA) using CouchDB or Cloudant. diff --git a/config/default.config.js b/config/default.config.js index 012a3862..ee9dbad5 100644 --- a/config/default.config.js +++ b/config/default.config.js @@ -1,4 +1,4 @@ -var path = require('path'); +var path = require('path') // These are the default settings that will be used if you don't override them in your config module.exports = { @@ -11,6 +11,10 @@ module.exports = { loginOnRegistration: false, loginOnPasswordReset: false }, + authStrategies: { + userPass: true, + apiToken: true + }, local: { usernameField: 'username', passwordField: 'password' @@ -41,4 +45,4 @@ module.exports = { format: 'text' } } -}; +} diff --git a/lib/local.js b/lib/local.js index 6eba334c..a7af0ba7 100644 --- a/lib/local.js +++ b/lib/local.js @@ -1,35 +1,37 @@ -'use strict'; -var util = require('./util'); -var LocalStrategy = require('passport-local'); -var BearerStrategy = require('passport-http-bearer-sl').Strategy; - +'use strict' +var util = require('./util') +var LocalStrategy = require('passport-local') +var BearerStrategy = require('passport-http-bearer-sl').Strategy module.exports = function (config, passport, user) { - // API token strategy - passport.use(new BearerStrategy( - function (tokenPass, done) { - var parse = tokenPass.split(':'); - if(parse.length < 2) { - done(null, false, {message: 'invalid token'}); + if (config.authStrategies.apiToken) { + passport.use(new BearerStrategy( + function (tokenPass, done) { + var parse = tokenPass.split(':') + if (parse.length < 2) { + done(null, false, { message: 'invalid token' }) + } else { + var token = parse[0] + var password = parse[1] + user.confirmSession(token, password) + .then(function (theuser) { + done(null, theuser) + }, function (err) { + if (err instanceof Error) { + done(err, false) + } else { + done(null, false, { message: err }) + } + }) + } } - var token = parse[0]; - var password = parse[1]; - user.confirmSession(token, password) - .then(function (theuser) { - done(null, theuser); - }, function (err) { - if (err instanceof Error) { - done(err, false); - } else { - done(null, false, {message: err}); - } - }); - } - )); + )) + } // Use local strategy - passport.use(new LocalStrategy({ + if (config.authStrategies.userPass) { + passport.use(new LocalStrategy({ usernameField: config.getItem('local.usernameField') || 'username', passwordField: config.getItem('local.passwordField') || 'password', session: false, @@ -40,65 +42,65 @@ module.exports = function (config, passport, user) { .then(function (theuser) { if (theuser) { // Check if the account is locked - if(theuser.local && theuser.local.lockedUntil && theuser.local.lockedUntil > Date.now()) { + if (theuser.local && theuser.local.lockedUntil && theuser.local.lockedUntil > Date.now()) { return done(null, false, { error: 'Unauthorized', message: 'Your account is currently locked. Please wait a few minutes and try again.' - }); + }) } - if(!theuser.local || !theuser.local.derived_key) { + if (!theuser.local || !theuser.local.derived_key) { return done(null, false, { error: 'Unauthorized', message: 'Invalid username or password' - }); + }) } util.verifyPassword(theuser.local, password) .then(function () { // Check if the email has been confirmed if it is required - if(config.getItem('local.requireEmailConfirm') && !theuser.email) { + if (config.getItem('local.requireEmailConfirm') && !theuser.email) { return done(null, false, { error: 'Unauthorized', message: 'You must confirm your email address.' - }); + }) } // Success!!! - return done(null, theuser); + return done(null, theuser) }, function (err) { if (!err) { // Password didn't authenticate - return handleFailedLogin(theuser, req, done); + return handleFailedLogin(theuser, req, done) } else { // Hashing function threw an error - return done(err); + return done(err) } - }); + }) } else { // user not found return done(null, false, { error: 'Unauthorized', message: 'Invalid username or password' - }); + }) } }, function (err) { // Database threw an error - return done(err); - }); + return done(err) + }) } - )); + )) + } - function handleFailedLogin(userDoc, req, done) { + function handleFailedLogin (userDoc, req, done) { var invalid = { error: 'Unauthorized', message: 'Invalid username or password' - }; + } return user.handleFailedLogin(userDoc, req) - .then(function(locked) { - if(locked) { + .then(function (locked) { + if (locked) { invalid.message = 'Maximum failed login attempts exceeded. Your account has been locked for ' + - Math.round(config.getItem('security.lockoutTime') / 60) + ' minutes.'; + Math.round(config.getItem('security.lockoutTime') / 60) + ' minutes.' } - return done(null, false, invalid); - }); + return done(null, false, invalid) + }) } - -}; +} diff --git a/lib/routes.js b/lib/routes.js index 8ccebe89..c4cf6634 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,267 +1,266 @@ -'use strict'; -var util = require('./util'); +'use strict' +var util = require('./util') -module.exports = function(config, router, passport, user) { +module.exports = function (config, router, passport, user) { + var env = process.env.NODE_ENV || 'development' - var env = process.env.NODE_ENV || 'development'; - - router.post('/login', function(req, res, next) { - passport.authenticate('local', function(err, user, info) { - if(err) { - return next(err); + router.post('/login', function (req, res, next) { + passport.authenticate('local', function (err, user, info) { + if (err) { + return next(err) } - if(!user) { - // Authentication failed - console.error('login 401', info) - return res.status(401).json(info); + if (!user) { + // Authentication failed + console.error('login 401', info) + return res.status(401).json(info) } // Success - req.logIn(user, {session: false}, function(err) { + req.logIn(user, { session: false }, function (err) { if (err) { - return next(err); + return next(err) } - }); - return next(); - })(req, res, next); - }, function (req, res, next) { - // Success handler - return user.createSession(req.user._id, 'local', req) - .then(function (mySession) { - res.status(200).json(mySession); - }, function (err) { - return next(err); - }); - }); + }) + return next() + })(req, res, next) + }, function (req, res, next) { + // Success handler + return user.createSession(req.user._id, 'local', req) + .then(function (mySession) { + res.status(200).json(mySession) + }, function (err) { + return next(err) + }) + }) router.post('/refresh', - passport.authenticate('bearer', {session: false}), + passport.authenticate('bearer', { session: false }), function (req, res, next) { return user.refreshSession(req.user.key) .then(function (mySession) { - res.status(200).json(mySession); + res.status(200).json(mySession) }, function (err) { - return next(err); - }); - }); + return next(err) + }) + }) router.post('/logout', function (req, res, next) { - var sessionToken = util.getSessionToken(req); - if(!sessionToken) { + var sessionToken = util.getSessionToken(req) + if (!sessionToken) { return next({ error: 'unauthorized', status: 401 - }); + }) } user.logoutSession(sessionToken) .then(function () { - res.status(200).json({ok: true, success: 'Logged out'}); + res.status(200).json({ ok: true, success: 'Logged out' }) }, function (err) { - console.error('Logout failed'); - return next(err); - }); - }); + console.error('Logout failed') + return next(err) + }) + }) router.post('/logout-others', - passport.authenticate('bearer', {session: false}), + passport.authenticate('bearer', { session: false }), function (req, res, next) { user.logoutOthers(req.user.key) .then(function () { - res.status(200).json({success: 'Other sessions logged out'}); + res.status(200).json({ success: 'Other sessions logged out' }) }, function (err) { - console.error('Logout failed'); - return next(err); - }); - }); + console.error('Logout failed') + return next(err) + }) + }) router.post('/logout-all', function (req, res, next) { - var sessionToken = util.getSessionToken(req); - if(!sessionToken) { + var sessionToken = util.getSessionToken(req) + if (!sessionToken) { return next({ error: 'unauthorized', status: 401 - }); + }) } user.logoutUser(null, sessionToken) .then(function () { - res.status(200).json({success: 'Logged out'}); + res.status(200).json({ success: 'Logged out' }) }, function (err) { - console.error('Logout-all failed'); - return next(err); - }); - }); + console.error('Logout-all failed') + return next(err) + }) + }) // Setting up the auth api router.post('/register', function (req, res, next) { - user.create(req.body, req) - .then(function (newUser) { - if(config.getItem('security.loginOnRegistration')) { - return user.createSession(newUser._id, 'local', req.ip) - .then(function (mySession) { - res.status(200).json(mySession); - }, function (err) { - return next(err); - }); - } else { - res.status(201).json({success: 'User created.'}); - } - }, function (err) { - res.status(err.status).json(err); - }); - }); + user.create(req.body, req) + .then(function (newUser) { + if (config.getItem('security.loginOnRegistration')) { + return user.createSession(newUser._id, 'local', req.ip) + .then(function (mySession) { + res.status(200).json(mySession) + }, function (err) { + return next(err) + }) + } else { + res.status(201).json({ success: 'User created.' }) + } + }, function (err) { + res.status(err.status).json(err) + }) + }) router.post('/forgot-password', function (req, res, next) { - user.forgotPassword(req.body.email, req).then(function () { - res.status(200).json({success: 'Password recovery email sent.'}); - }, function (err) { - return next(err); - }); - }); + user.forgotPassword(req.body.email, req).then(function () { + res.status(200).json({ success: 'Password recovery email sent.' }) + }, function (err) { + return next(err) + }) + }) router.post('/password-reset', function (req, res, next) { - user.resetPassword(req.body, req) - .then(function (currentUser) { - if(config.getItem('security.loginOnPasswordReset')) { - return user.createSession(currentUser._id, 'local', req.ip) - .then(function (mySession) { - res.status(200).json(mySession); - }, function (err) { - return next(err); - }); - } else { - res.status(200).json({success: 'Password successfully reset.'}); - } - }, function (err) { - return next(err); - }); - }); + user.resetPassword(req.body, req) + .then(function (currentUser) { + if (config.getItem('security.loginOnPasswordReset')) { + return user.createSession(currentUser._id, 'local', req.ip) + .then(function (mySession) { + res.status(200).json(mySession) + }, function (err) { + return next(err) + }) + } else { + res.status(200).json({ success: 'Password successfully reset.' }) + } + }, function (err) { + return next(err) + }) + }) router.post('/password-change', - passport.authenticate('bearer', {session: false}), + passport.authenticate('bearer', { session: false }), function (req, res, next) { user.changePasswordSecure(req.user._id, req.body, req) .then(function () { - res.status(200).json({success: 'password changed'}); + res.status(200).json({ success: 'password changed' }) }, function (err) { - return next(err); - }); - }); + return next(err) + }) + }) router.post('/unlink/:provider', - passport.authenticate('bearer', {session: false}), - function(req, res, next) { - var provider = req.params.provider; + passport.authenticate('bearer', { session: false }), + function (req, res, next) { + var provider = req.params.provider user.unlink(req.user._id, provider) - .then(function() { - res.status(200).json({success: util.capitalizeFirstLetter(provider) + ' unlinked'}); + .then(function () { + res.status(200).json({ success: util.capitalizeFirstLetter(provider) + ' unlinked' }) }, function (err) { - return next(err); - }); - }); + return next(err) + }) + }) router.get('/confirm-email/:token', function (req, res, next) { - var redirectURL = config.getItem('local.confirmEmailRedirectURL'); + var redirectURL = config.getItem('local.confirmEmailRedirectURL') if (!req.params.token) { - var err = {error: 'Email verification token required'}; - if(redirectURL) { - return res.status(201).redirect(redirectURL + '?error=' + encodeURIComponent(err.error)); + var err = { error: 'Email verification token required' } + if (redirectURL) { + return res.status(201).redirect(redirectURL + '?error=' + encodeURIComponent(err.error)) } - return res.status(400).send(err); + return res.status(400).send(err) } user.verifyEmail(req.params.token, req).then(function () { - if(redirectURL) { - return res.status(201).redirect(redirectURL + '?success=true'); + if (redirectURL) { + return res.status(201).redirect(redirectURL + '?success=true') } - res.status(200).send({ok: true, success: 'Email verified'}); + res.status(200).send({ ok: true, success: 'Email verified' }) }, function (err) { - if(redirectURL) { - var query = '?error=' + encodeURIComponent(err.error); - if(err.message) { - query += '&message=' + encodeURIComponent(err.message); + if (redirectURL) { + var query = '?error=' + encodeURIComponent(err.error) + if (err.message) { + query += '&message=' + encodeURIComponent(err.message) } - return res.status(201).redirect(redirectURL + query); + return res.status(201).redirect(redirectURL + query) } - return next(err); - }); - }); + return next(err) + }) + }) router.get('/validate-username/:username', - function(req, res, next) { - if(!req.params.username) { - return next({error: 'Username required', status: 400}); + function (req, res, next) { + if (!req.params.username) { + return next({ error: 'Username required', status: 400 }) } user.validateUsername(req.params.username) - .then(function(err) { - if(!err) { - res.status(200).json({ok: true}); + .then(function (err) { + if (!err) { + res.status(200).json({ ok: true }) } else { - res.status(409).json({error: 'Username already in use'}); + res.status(409).json({ error: 'Username already in use' }) } - }, function(err) { - return next(err); - }); + }, function (err) { + return next(err) + }) } - ); + ) router.get('/validate-email/:email', - function(req, res, next) { - var promise; - if(!req.params.email) { - return next({error: 'Email required', status: 400}); + function (req, res, next) { + var promise + if (!req.params.email) { + return next({ error: 'Email required', status: 400 }) } - if(config.getItem('local.emailUsername')) { - promise = user.validateEmailUsername(req.params.email); + if (config.getItem('local.emailUsername')) { + promise = user.validateEmailUsername(req.params.email) } else { - promise = user.validateEmail(req.params.email); + promise = user.validateEmail(req.params.email) } promise - .then(function(err) { - if(!err) { - res.status(200).json({ok: true}); + .then(function (err) { + if (!err) { + res.status(200).json({ ok: true }) } else { - res.status(409).json({error: 'Email already in use'}); + res.status(409).json({ error: 'Email already in use' }) } - }, function(err) { - return next(err); - }); + }, function (err) { + return next(err) + }) } - ); + ) router.post('/change-email', - passport.authenticate('bearer', {session: false}), + passport.authenticate('bearer', { session: false }), function (req, res, next) { user.changeEmail(req.user._id, req.body.newEmail, req) .then(function () { - res.status(200).json({ok: true, success: 'Email changed'}); + res.status(200).json({ ok: true, success: 'Email changed' }) }, function (err) { - return next(err); - }); - }); + return next(err) + }) + }) // route to test token authentication router.get('/session', - passport.authenticate('bearer', {session: false}), + passport.authenticate('bearer', { session: false }), function (req, res) { - var user = req.user; - user.user_id = user._id; - delete user._id; + var user = req.user + user.user_id = user._id + delete user._id // user.token = user.key; - delete user.key; - res.status(200).json(user); - }); + delete user.key + res.status(200).json(user) + }) // Error handling - router.use(function(err, req, res) { - console.error(err); - if(err.stack) { - console.error(err.stack); + router.use(function (err, req, res) { + console.error(err) + if (err.stack) { + console.error(err.stack) } - res.status(err.status || 500); - if(err.stack && env !== 'development') { - delete err.stack; + res.status(err.status || 500) + if (err.stack && env !== 'development') { + delete err.stack } - res.json(err); - }); + res.json(err) + }) }; diff --git a/package.json b/package.json index b072738d..c46f9ead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@sensu/superlogin", - "version": "1.2.1", + "name": "@jaredthirsk/superlogin", + "version": "1.3.0", "description": "Powerful authentication for APIs and single page apps using the CouchDB ecosystem which supports a variety of providers.", "main": "./lib/index.js", "repository": { @@ -71,4 +71,4 @@ "sinon": "^3.3.0", "sinon-chai": "^2.14.0" } -} +} \ No newline at end of file diff --git a/superlogin.code-workspace b/superlogin.code-workspace new file mode 100644 index 00000000..876a1499 --- /dev/null +++ b/superlogin.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file