From 4404f390e324ef7592f980ea6f3a09b99e143971 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 24 Feb 2018 21:41:35 +0000 Subject: [PATCH] Added fuzzer to tests --- config/routes/auth.js | 346 +++++++++++++++++++++++------------------- config/test.js | 2 +- test/auth.js | 58 +++---- 3 files changed, 220 insertions(+), 186 deletions(-) diff --git a/config/routes/auth.js b/config/routes/auth.js index 9f00565..d72221b 100755 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -57,7 +57,7 @@ module.exports = (app, passport) => { .post( async (req, res, next) => { // Send token and alert user - async function sendToken(user) { + const sendToken = async function(user) { debug(`sendToken() called for user ${user.id}`) // Create a new password token @@ -131,114 +131,124 @@ module.exports = (app, passport) => { } - // Validate email - req.checkBody('email', 'Please enter a valid email address.').isEmail() + // Invalid email + if (!mw.validateEmail(req.body.email)) { + debug(`Email ${req.body.email} was found invalid!`) + req.flash('warning', `The email you entered, ${req.body.email} isn't valid. Try again. `) + res.redirect('/login#login') + next() - // Check if somebody already has that email - try { - debug(`Searching for user with email ${req.body.email}...`) - let user = await User.findOne({'email': req.body.email}) + // Valid email + } else { + debug(`Email ${req.body.email} was found valid.`) - // User already exists - if (user && user.auth.password) { - debug(`User ${user.id} has email ${req.body.email} and has a password`) - req.flash('warning', - `A user with that email already exists! If you forgot your password, \ - you can reset it here.` - ) - res.redirect('/login#login') - next() + // Check if somebody already has that email + try { + debug(`Searching for user with email ${req.body.email}...`) + let user = await User.findOne({'email': req.body.email}) - // User exists but hasn't created a password yet - } else if (user) { - debug(`User ${user.id} has email ${req.body.email} but doesn't have a password`) + // User already exists + if (user && user.auth.password) { + debug(`User ${user.id} has email ${req.body.email} and has a password`) + req.flash('warning', + `A user with that email already exists! If you forgot your password, \ + you can reset it here.` + ) + res.redirect('/login#login') + next() - // Send another token - sendToken(user) + // User exists but hasn't created a password yet + } else if (user) { + debug(`User ${user.id} has email ${req.body.email} but doesn't have a password`) - // Create user - } else { - debug(`User with email ${req.body.email} doesn't exist; creating one`) - - let email = req.body.email - - user = new User() - user.created = Date.now() - user.email = email - user.slug = slugify(email.substring(0, email.indexOf('@'))) - - // Generate unique slug - const slug = new Promise((resolve, reject) => { - debug(`Creating new slug for user...`); - - (async function checkSlug (s, cb) { - try { - debug(`Checking to see if slug ${s} is taken...`) - let existingUser = await User.findOne({slug: s}) - - // Slug in use: generate a random one and retry - if (existingUser) { - debug(`Slug ${s} is taken; generating another...`) - crypto.randomBytes(6, (err, buf) => { - if (err) { - debug('Failed to create random bytes for slug!') - mw.throwErr(err, req) - reject() - } - if (buf) { - checkSlug(buf.toString('hex'), cb) - } - }) - - // Unique slug: proceed - } else { - debug(`Slug ${s} is unique`) - cb(s) - } - } catch (err) { - debug('Failed to create slug!') - mw.throwErr(err, req) - reject() - } - - })(user.slug, (newSlug) => { - debug(`Successfully created slug: ${newSlug}`) - user.slug = newSlug - resolve() - }) - }) - - // Generate sk32 - const sk32 = new Promise((resolve, reject) => { - debug('Creating sk32 for user...') - crypto.randomBytes(32, (err, buf) => { - if (err) { - debug('Failed to create sk32!') - mw.throwErr(err, req) - reject() - } - if (buf) { - user.sk32 = buf.toString('hex') - debug(`Successfully created sk32: ${user.sk32}`) - resolve() - } - }) - }) - - // Save user and send the token by email - try { - await Promise.all([slug, sk32]) + // Send another token sendToken(user) - } catch (err) { - debug('Failed to save user after creating slug and sk32!') - mw.throwErr(err, req) - res.redirect('/login#signup') + + // Create user + } else { + debug(`User with email ${req.body.email} doesn't exist; creating one`) + + let email = req.body.email + + user = new User() + user.created = Date.now() + user.email = email + user.slug = slugify(email.substring(0, email.indexOf('@'))) + + // Generate unique slug + const slug = new Promise((resolve, reject) => { + debug(`Creating new slug for user...`); + + (async function checkSlug (s, cb) { + try { + debug(`Checking to see if slug ${s} is taken...`) + let existingUser = await User.findOne({slug: s}) + + // Slug in use: generate a random one and retry + if (existingUser) { + debug(`Slug ${s} is taken; generating another...`) + crypto.randomBytes(6, (err, buf) => { + if (err) { + debug('Failed to create random bytes for slug!') + mw.throwErr(err, req) + reject() + } + if (buf) { + checkSlug(buf.toString('hex'), cb) + } + }) + + // Unique slug: proceed + } else { + debug(`Slug ${s} is unique`) + cb(s) + } + } catch (err) { + debug('Failed to create slug!') + mw.throwErr(err, req) + reject() + } + + })(user.slug, (newSlug) => { + debug(`Successfully created slug: ${newSlug}`) + user.slug = newSlug + resolve() + }) + }) + + // Generate sk32 + const sk32 = new Promise((resolve, reject) => { + debug('Creating sk32 for user...') + crypto.randomBytes(32, (err, buf) => { + if (err) { + debug('Failed to create sk32!') + mw.throwErr(err, req) + reject() + } + if (buf) { + user.sk32 = buf.toString('hex') + debug(`Successfully created sk32: ${user.sk32}`) + resolve() + } + }) + }) + + // Save user and send the token by email + try { + await Promise.all([slug, sk32]) + sendToken(user) + } catch (err) { + debug('Failed to save user after creating slug and sk32!') + mw.throwErr(err, req) + res.redirect('/login#signup') + } } + } catch (err) { + debug(`Failed to check if somebody already has the email ${req.body.email}`) + mw.throwErr(err, req) + res.redirect('/login#signup') } - } catch (err) { - debug(`Failed to check if somebody already has the email ${req.body.email}`) - mw.throwErr(err, req) - res.redirect('/login#signup') + } }) @@ -259,65 +269,84 @@ module.exports = (app, passport) => { // Submitted forgot password form .post( async (req, res, next) => { - // Validate email - req.checkBody('email', 'Please enter a valid email address.').isEmail() - // Check if somebody has that email - try { - let user = await User.findOne({'email': req.body.email}) - - // No user with that email - if (!user) { - // Don't let on that no such user exists, to prevent dictionary attacks - req.flash('success', - `If an account exists with the email ${req.body.email}, \ - an email has been sent there with a password reset link. ` - ) - res.redirect('/login') - - // User with that email does exist - } else { - - // Create reset token - try { - let [token, expires] = await user.createPassToken() - - // Email reset link - try { - await mail.send({ - from: mail.noReply, - to: mail.to(user), - subject: 'Reset your Tracman password', - text: mail.text( - `Hi, \n\nDid you request to reset your Tracman password? \ - If so, follow this link to do so:\ - \n${env.url}/account/password/${token}\n\n\ - If you didn't initiate this request, just ignore this email. ` - ), - html: mail.html( - `

Hi,

Did you request to reset your Tracman password? \ - If so, follow this link to do so:
\ - \ - ${env.url}/account/password/${token}

\ -

If you didn't initiate this request, just ignore this email.

` - ) - }) - req.flash( - 'success', - `If an account exists with the email ${req.body.email}, \ - an email has been sent there with a password reset link. `) - res.redirect('/login') - } catch (err) { - debug(`Failed to send reset link to ${user.email}`) - mw.throwErr(err, req) - res.redirect('/login') - } - } catch (err) { return next(err) } - } - } catch (err) { - debug(`Failed to check for if somebody has that email (in reset request)!`) - mw.throwErr(err, req) + // Invalid email + if (!mw.validateEmail(req.body.email)) { + debug(`Email ${req.body.email} was found invalid!`) + req.flash('warning', `The email you entered, ${req.body.email} isn't valid. Try again. `) res.redirect('/login/forgot') + next() + + // Valid email + } else { + + // Check if somebody has that email + try { + let user = await User.findOne({'email': req.body.email}) + + // No user with that email + if (!user) { + // Don't let on that no such user exists, to prevent dictionary attacks + req.flash('success', + `If an account exists with the email ${req.body.email}, \ + an email has been sent there with a password reset link. ` + ) + res.redirect('/login') + + // User with that email does exist + } else { + + // Create reset token + try { + let [token, expires] = await user.createPassToken() + + // Figure out expiration time string + debug(`Determining expiration time string for ${expires}...`) + let expiration_time_string = (req.query.tz) + ? moment(expires).utcOffset(req.query.tz).toDate().toLocaleTimeString(req.acceptsLanguages[0]) + : moment(expires).toDate().toLocaleTimeString(req.acceptsLanguages[0]) + ' UTC' + + // Email reset link + try { + await mail.send({ + from: mail.noReply, + to: mail.to(user), + subject: 'Reset your Tracman password', + text: mail.text( + `Hi, \n\nDid you request to reset your Tracman password? \ + If so, follow this link to do so:\ + \n${env.url}/account/password/${token}\n\n\ + This link will expire at ${expiration_time_string}. \n\n\ + If you didn't initiate this request, just ignore this email. \n\n` + ), + html: mail.html( + `

Hi,

Did you request to reset your Tracman password? \ + If so, follow this link to do so:
\ + \ + ${env.url}/account/password/${token}. \ + This link will expire at ${expiration_time_string}.

\ +

If you didn't initiate this request, just ignore this email.

` + ) + }) + req.flash( + 'success', + `If an account exists with the email ${req.body.email}, \ + an email has been sent there with a password reset link.\ + (Your reset link will expire in one hour.)`) + res.redirect('/login') + } catch (err) { + debug(`Failed to send reset link to ${user.email}`) + mw.throwErr(err, req) + res.redirect('/login') + } + } catch (err) { return next(err) } + } + } catch (err) { + debug(`Failed to check for if somebody has that email (in reset request)!`) + mw.throwErr(err, req) + res.redirect('/login/forgot') + } + } }) @@ -377,4 +406,5 @@ module.exports = (app, passport) => { app.get('/login/google/cb', passport.authenticate('google', loginOutcome), loginCallback) app.get('/login/facebook/cb', passport.authenticate('facebook', loginOutcome), loginCallback) app.get('/login/twitter/cb', passport.authenticate('twitter', loginOutcome), loginCallback) + } diff --git a/config/test.js b/config/test.js index 377c86a..f654dd3 100755 --- a/config/test.js +++ b/config/test.js @@ -4,5 +4,5 @@ module.exports = { TEST_PASSWORD: 'mDAQYe2VYE', BAD_PASSWORD: 'password123', FUZZED_EMAIL_TRIES: 3, - FUZZED_PASSWORD_TRIES: 10, + FUZZED_PASSWORD_TRIES: 100, } \ No newline at end of file diff --git a/test/auth.js b/test/auth.js index 4b6d779..72b7a87 100755 --- a/test/auth.js +++ b/test/auth.js @@ -2,6 +2,7 @@ const chai = require('chai') const app = require('../server') +const froth = require('mocha-froth') const User = require('../config/models').user // const superagent = require('superagent').agent() const request = require('supertest').agent(app) @@ -49,27 +50,28 @@ describe('Authentication', () => { }) - // TODO: Implement fuzzer - it.skip(`Fails to create accounts with ${FUZZED_EMAIL_TRIES} fuzzed emails`, () => { + it(`Fails to create accounts with ${FUZZED_EMAIL_TRIES} fuzzed emails`, () => { // Fuzz emails - // loop with let fuzzed_email + froth(FUZZED_EMAIL_TRIES).forEach( async (fuzzed_email) => { // Confirm redirect - // chai.expect( await request.post('/signup') - // .type('form').send({ 'email':fuzzed_email }) - // ).to.redirectTo('/login#signup') + chai.expect( await request.post('/signup') + .type('form').send({ 'email':fuzzed_email }) + ).to.redirectTo('/login#signup') - /* Ensure user was deleted after email failed to send - /* Users with bad emails are removed asynchronously and may happen after - /* the response was recieved. Ensure it's happened in a kludgy way by - /* waiting 2 seconds before asserting that the user doesn't exist - */ - // setTimeout( async () => { - // chai.assert.isNull( await User.findOne({ - // 'email': FAKE_EMAIL - // }), 'Account with fake email was created') - // }, 2000) + /* Ensure user was deleted after email failed to send + /* Users with bad emails are removed asynchronously and may happen after + /* the response was recieved. Ensure it's happened in a kludgy way by + /* waiting 2 seconds before asserting that the user doesn't exist + */ + setTimeout( async () => { + chai.assert.isNull( await User.findOne({ + 'email': fuzzed_email + }), 'Account with fake email was created') + }, 2000) + + }) }) @@ -140,23 +142,24 @@ describe('Authentication', () => { }) - // TODO: Implement fuzzer - it.skip(`Fails to log in with ${FUZZED_PASSWORD_TRIES} fuzzed passwords`, () => { + it(`Fails to log in with ${FUZZED_PASSWORD_TRIES} fuzzed passwords`, () => { // Fuzz passwords - // loop with let fuzzed_password + froth(FUZZED_PASSWORD_TRIES).forEach( async (fuzzed_password) => { - // Confirm redirect - // chai.expect( await request.post('/login') - // .type('form').send({ - // 'email': TEST_EMAIL, - // 'password': fuzzed_password - // }) - // ).to.redirectTo('/login') // Hey! Incorrect email or password. + // Confirm redirect + chai.expect( await request.post('/login') + .type('form').send({ + 'email': TEST_EMAIL, + 'password': fuzzed_password + }) + ).to.redirectTo('/login') // Hey! Incorrect email or password. + + }) }) - it('Loads forgot password page', async () => { + it.skip('Loads forgot password page', async () => { let res = await request.get('/login/forgot') chai.expect(res).html.to.have.status(200) }) @@ -257,6 +260,7 @@ describe('Authentication', () => { }) }) + }) })