Added fuzzer to tests

master
Keith Irwin 2018-02-24 21:41:35 +00:00
parent c36104aa9c
commit 4404f390e3
No known key found for this signature in database
GPG Key ID: 378933C743E2BBC0
3 changed files with 220 additions and 186 deletions

View File

@ -57,7 +57,7 @@ module.exports = (app, passport) => {
.post( async (req, res, next) => { .post( async (req, res, next) => {
// Send token and alert user // Send token and alert user
async function sendToken(user) { const sendToken = async function(user) {
debug(`sendToken() called for user ${user.id}`) debug(`sendToken() called for user ${user.id}`)
// Create a new password token // Create a new password token
@ -131,114 +131,124 @@ module.exports = (app, passport) => {
} }
// Validate email // Invalid email
req.checkBody('email', 'Please enter a valid email address.').isEmail() 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 // Valid email
try { } else {
debug(`Searching for user with email ${req.body.email}...`) debug(`Email ${req.body.email} was found valid.`)
let user = await User.findOne({'email': req.body.email})
// User already exists // Check if somebody already has that email
if (user && user.auth.password) { try {
debug(`User ${user.id} has email ${req.body.email} and has a password`) debug(`Searching for user with email ${req.body.email}...`)
req.flash('warning', let user = await User.findOne({'email': req.body.email})
`A user with that email already exists! If you forgot your password, \
you can <a href="/login/forgot?email=${req.body.email}">reset it here</a>.`
)
res.redirect('/login#login')
next()
// User exists but hasn't created a password yet // User already exists
} else if (user) { if (user && user.auth.password) {
debug(`User ${user.id} has email ${req.body.email} but doesn't have a 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 <a href="/login/forgot?email=${req.body.email}">reset it here</a>.`
)
res.redirect('/login#login')
next()
// Send another token // User exists but hasn't created a password yet
sendToken(user) } else if (user) {
debug(`User ${user.id} has email ${req.body.email} but doesn't have a password`)
// Create user // Send another token
} 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) sendToken(user)
} catch (err) {
debug('Failed to save user after creating slug and sk32!') // Create user
mw.throwErr(err, req) } else {
res.redirect('/login#signup') 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 // Submitted forgot password form
.post( async (req, res, next) => { .post( async (req, res, next) => {
// Validate email
req.checkBody('email', 'Please enter a valid email address.').isEmail()
// Check if somebody has that email // Invalid email
try { if (!mw.validateEmail(req.body.email)) {
let user = await User.findOne({'email': 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. `)
// 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 <u>${req.body.email}</u>, \
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(
`<p>Hi, </p><p>Did you request to reset your Tracman password? \
If so, follow this link to do so:<br>\
<a href="${env.url}/account/password/${token}">\
${env.url}/account/password/${token}</a></p>\
<p>If you didn't initiate this request, just ignore this email. </p>`
)
})
req.flash(
'success',
`If an account exists with the email <u>${req.body.email}</u>, \
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)
res.redirect('/login/forgot') 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 <u>${req.body.email}</u>, \
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(
`<p>Hi, </p><p>Did you request to reset your Tracman password? \
If so, follow this link to do so:<br>\
<a href="${env.url}/account/password/${token}">\
${env.url}/account/password/${token}</a>. \
This link will expire at ${expiration_time_string}. </p>\
<p>If you didn't initiate this request, just ignore this email. </p>`
)
})
req.flash(
'success',
`If an account exists with the email <u>${req.body.email}</u>, \
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/google/cb', passport.authenticate('google', loginOutcome), loginCallback)
app.get('/login/facebook/cb', passport.authenticate('facebook', loginOutcome), loginCallback) app.get('/login/facebook/cb', passport.authenticate('facebook', loginOutcome), loginCallback)
app.get('/login/twitter/cb', passport.authenticate('twitter', loginOutcome), loginCallback) app.get('/login/twitter/cb', passport.authenticate('twitter', loginOutcome), loginCallback)
} }

View File

@ -4,5 +4,5 @@ module.exports = {
TEST_PASSWORD: 'mDAQYe2VYE', TEST_PASSWORD: 'mDAQYe2VYE',
BAD_PASSWORD: 'password123', BAD_PASSWORD: 'password123',
FUZZED_EMAIL_TRIES: 3, FUZZED_EMAIL_TRIES: 3,
FUZZED_PASSWORD_TRIES: 10, FUZZED_PASSWORD_TRIES: 100,
} }

View File

@ -2,6 +2,7 @@
const chai = require('chai') const chai = require('chai')
const app = require('../server') const app = require('../server')
const froth = require('mocha-froth')
const User = require('../config/models').user const User = require('../config/models').user
// const superagent = require('superagent').agent() // const superagent = require('superagent').agent()
const request = require('supertest').agent(app) const request = require('supertest').agent(app)
@ -49,27 +50,28 @@ describe('Authentication', () => {
}) })
// TODO: Implement fuzzer it(`Fails to create accounts with ${FUZZED_EMAIL_TRIES} fuzzed emails`, () => {
it.skip(`Fails to create accounts with ${FUZZED_EMAIL_TRIES} fuzzed emails`, () => {
// Fuzz emails // Fuzz emails
// loop with let fuzzed_email froth(FUZZED_EMAIL_TRIES).forEach( async (fuzzed_email) => {
// Confirm redirect // Confirm redirect
// chai.expect( await request.post('/signup') chai.expect( await request.post('/signup')
// .type('form').send({ 'email':fuzzed_email }) .type('form').send({ 'email':fuzzed_email })
// ).to.redirectTo('/login#signup') ).to.redirectTo('/login#signup')
/* Ensure user was deleted after email failed to send /* Ensure user was deleted after email failed to send
/* Users with bad emails are removed asynchronously and may happen after /* Users with bad emails are removed asynchronously and may happen after
/* the response was recieved. Ensure it's happened in a kludgy way by /* the response was recieved. Ensure it's happened in a kludgy way by
/* waiting 2 seconds before asserting that the user doesn't exist /* waiting 2 seconds before asserting that the user doesn't exist
*/ */
// setTimeout( async () => { setTimeout( async () => {
// chai.assert.isNull( await User.findOne({ chai.assert.isNull( await User.findOne({
// 'email': FAKE_EMAIL 'email': fuzzed_email
// }), 'Account with fake email was created') }), 'Account with fake email was created')
// }, 2000) }, 2000)
})
}) })
@ -140,23 +142,24 @@ describe('Authentication', () => {
}) })
// TODO: Implement fuzzer it(`Fails to log in with ${FUZZED_PASSWORD_TRIES} fuzzed passwords`, () => {
it.skip(`Fails to log in with ${FUZZED_PASSWORD_TRIES} fuzzed passwords`, () => {
// Fuzz passwords // Fuzz passwords
// loop with let fuzzed_password froth(FUZZED_PASSWORD_TRIES).forEach( async (fuzzed_password) => {
// Confirm redirect // Confirm redirect
// chai.expect( await request.post('/login') chai.expect( await request.post('/login')
// .type('form').send({ .type('form').send({
// 'email': TEST_EMAIL, 'email': TEST_EMAIL,
// 'password': fuzzed_password 'password': fuzzed_password
// }) })
// ).to.redirectTo('/login') // Hey! Incorrect email or 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') let res = await request.get('/login/forgot')
chai.expect(res).html.to.have.status(200) chai.expect(res).html.to.have.status(200)
}) })
@ -257,6 +260,7 @@ describe('Authentication', () => {
}) })
}) })
}) })
}) })