Fixing last couple of auth dialogs

This commit is contained in:
John Lyon-Smith
2018-03-24 10:53:34 -07:00
parent ce25d56dfe
commit cb708c720f
16 changed files with 312 additions and 274 deletions

View File

@@ -43,6 +43,10 @@ export class AuthRoutes {
app.route('/auth/password/send')
.post(this.sendPasswordResetEmail)
// Confirm a password reset token is valid
app.route('/auth/password/confirm')
.post(this.confirmPasswordToken)
// Finish a password reset
app.route('/auth/password/reset')
.post(this.resetPassword)
@@ -52,110 +56,104 @@ export class AuthRoutes {
.get(passport.authenticate('bearer', { session: false }), this.whoAmI)
}
login(req, res, next) {
async login(req, res, next) {
const email = req.body.email
const password = req.body.password
if (!email || !password) {
return next(new createError.BadRequest('Must supply user name and password'))
}
let User = this.db.User
// Lookup the user
User.findOne({ email }).then((user) => {
try {
if (!email || !password) {
createError.BadRequest('Must supply user name and password')
}
// Lookup the user
const user = await User.findOne({ email })
if (!user) {
// NOTE: Don't return NotFound as that gives too much information away to hackers
return Promise.reject(createError.BadRequest("Email or password incorrect"))
throw createError.BadRequest("Email or password incorrect")
} else if (user.emailToken || !user.passwordHash) {
return Promise.reject(createError.Forbidden("Must confirm email and set password"))
} else {
let cr = credential()
return Promise.all([
Promise.resolve(user),
cr.verify(JSON.stringify(user.passwordHash), req.body.password)
])
throw createError.Forbidden("Must confirm email and set password")
}
}).then((arr) => {
const [user, isValid] = arr
let cr = credential()
const isValid = cr.verify(JSON.stringify(user.passwordHash), req.body.password)
if (isValid) {
user.loginToken = loginToken.pack(user._id.toString(), user.email)
} else {
user.loginToken = null // A bad login removes existing token for this user...
}
return user.save()
}).then((savedUser) => {
const savedUser = await user.save()
if (savedUser.loginToken) {
res.set('Authorization', `Bearer ${savedUser.loginToken}`)
res.json(savedUser.toClient())
} else {
return Promise.reject(createError.BadRequest('email or password incorrect'))
throw createError.BadRequest('email or password incorrect')
}
}).catch((err) => {
} catch(err) {
if (err instanceof createError.HttpError) {
next(err)
} else {
next(createError.InternalServerError(`${err ? err.message : ''}`))
next(createError.InternalServerError(err.message))
}
})
}
}
logout(req, res, next) {
async logout(req, res, next) {
let User = this.db.User
User.findById({ _id: req.user._id }).then((user) => {
if (!user) {
return next(createError.BadRequest())
try {
const user = await User.findById({ _id: req.user._id })
if (user) {
user.loginToken = null
await user.save()
}
user.loginToken = null
user.save().then((savedUser) => {
res.json({})
})
}).catch((err) => {
next(createError.InternalServerError(`Unable to login. ${err ? err.message : ''}`))
})
res.json({})
} catch(err) {
next(createError.InternalServerError(err.message))
}
}
whoAmI(req, res, next) {
res.json(req.user.toClient())
}
sendChangeEmailEmail(req, res, next) {
async sendChangeEmailEmail(req, res, next) {
let existingEmail = req.body.existingEmail
const newEmail = req.body.newEmail
let User = this.db.User
const isAdmin = !!req.user.administrator
if (existingEmail) {
if (!isAdmin) {
return next(createError.Forbidden('Only admins can resend change email to any user'))
try {
if (existingEmail) {
if (!isAdmin) {
throw createError.Forbidden('Only admins can resend change email to any user')
}
} else {
existingEmail = req.user.email
}
} else {
existingEmail = req.user.email
}
let promiseArray = [User.findOne({ email: existingEmail })]
const user = await User.findOne({ email: existingEmail })
let conflictingUser = null
if (newEmail) {
promiseArray.push(User.findOne({ email: newEmail }))
}
Promise.all(promiseArray).then((arr) => {
const [user, conflictingUser] = arr
if (newEmail) {
conflictingUser = await User.findOne({ email: newEmail })
}
if (!user) {
return Promise.reject(createError.NotFound(`User with email '${existingEmail}' was not found`))
throw createError.NotFound(`User with email '${existingEmail}' was not found`)
} else if (conflictingUser) {
return Promise.reject(createError.BadRequest(`A user with '${newEmail}' already exists`))
throw createError.BadRequest(`A user with '${newEmail}' already exists`)
} else if (!isAdmin && user.emailToken && (new Date() - user.emailToken.created) < this.sendEmailDelayInSeconds) {
return Promise.reject(createError.BadRequest('Cannot request email confirmation again so soon'))
throw createError.BadRequest('Cannot request email confirmation again so soon')
}
return Promise.all([Promise.resolve(user), util.promisify(crypto.randomBytes)(32)])
}).then((arr) => {
let [ user, buf ] = arr
const buf = await util.promisify(crypto.randomBytes)(32)
user.emailToken = {
value: urlSafeBase64.encode(buf),
@@ -166,8 +164,7 @@ export class AuthRoutes {
user.newEmail = newEmail
}
return user.save()
}).then((savedUser) => {
const savedUser = await user.save()
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
const siteUrl = url.parse(req.headers.referer)
let msgs = []
@@ -193,36 +190,41 @@ export class AuthRoutes {
supportEmail: this.supportEmail
}
})
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msgs) : Promise.resolve()
}).then(() => {
if (this.sendEmail) {
await this.mq.request('dar-email', 'sendEmail', msgs)
}
res.json({})
}).catch((err) => {
} catch(err) {
if (err instanceof createError.HttpError) {
next(err)
} else {
next(createError.InternalServerError(`Unable to send change email email. ${err.message}`))
next(createError.InternalServerError(err.message))
}
})
}
}
confirmEmail(req, res, next) {
async confirmEmail(req, res, next) {
const token = req.body.emailToken
let User = this.db.User
if (!token) {
return next(createError.BadRequest('Invalid request parameters'))
}
try {
if (!token) {
throw createError.BadRequest('Invalid request parameters')
}
const user = await User.findOne({ 'emailToken.value': token })
User.findOne({ 'emailToken.value': token }).then((user) => {
if (!user) {
return Promise.reject(createError.BadRequest(`The token was not found`))
throw createError.BadRequest(`The token was not found`)
}
// Token must not be too old
const ageInHours = (new Date() - user.emailToken.created) / 3600000
if (ageInHours > this.maxEmailTokenAgeInHours) {
return Promise.reject(createError.BadRequest(`Token has expired`))
throw createError.BadRequest(`Token has expired`)
}
// Remove the email token & any login token as it will become invalid
@@ -235,86 +237,109 @@ export class AuthRoutes {
user.newEmail = undefined
}
let promiseArray = [ Promise.resolve(user) ]
let buf = null
// If user has no password, create reset token for them
if (!user.passwordHash) {
// User has no password, create reset token for them
promiseArray.push(util.promisify(crypto.randomBytes)(32))
}
buf = await util.promisify(crypto.randomBytes)(32)
return Promise.all(promiseArray)
}).then((arr) => {
let [ user, buf ] = arr
if (buf) {
user.passwordToken = {
value: urlSafeBase64.encode(buf),
created: new Date()
}
}
return user.save()
}).then((savedUser) => {
const savedUser = await user.save()
let obj = {}
// Only because the user has sent us a valid email reset token can we respond with an password reset token
// Only because the user has sent us a valid email reset token
// can we respond with an password reset token.
if (savedUser.passwordToken) {
obj.passwordToken = savedUser.passwordToken.value
}
res.json(obj)
}).catch((err) => {
} catch(err) {
if (err instanceof createError.HttpError) {
next(err)
} else {
next(createError.InternalServerError(`Unable to confirm set email token. ${err.message}`))
next(createError.InternalServerError(err.message))
}
})
}
}
resetPassword(req, res, next) {
async confirmPasswordToken(req, res, next) {
const token = req.body.passwordToken
const newPassword = req.body.newPassword
let User = this.db.User
let cr = credential()
if (!token || !newPassword) {
return next(createError.BadRequest('Invalid request parameters'))
}
try {
if (!token) {
throw createError.BadRequest('Invalid request parameters')
}
const user = await User.findOne({ 'passwordToken.value': token })
User.findOne({ 'passwordToken.value': token }).then((user) => {
if (!user) {
return Promise.reject(createError.BadRequest(`The token was not found`))
throw createError.BadRequest(`The token was not found`)
}
// Token must not be too old
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
if (ageInHours > this.maxPasswordTokenAgeInHours) {
return Promise.reject(createError.BadRequest(`Token has expired`))
throw createError.BadRequest(`Token has expired`)
}
res.json({})
} catch (err) {
if (err instanceof createError.HttpError) {
next(err)
} else {
next(createError.InternalServerError(err.message))
}
}
}
async resetPassword(req, res, next) {
const token = req.body.passwordToken
const newPassword = req.body.newPassword
let User = this.db.User
let cr = credential()
try {
if (!token || !newPassword) {
throw createError.BadRequest('Invalid request parameters')
}
const user = await User.findOne({ 'passwordToken.value': token })
if (!user) {
throw createError.BadRequest(`The token was not found`)
}
// Token must not be too old
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
if (ageInHours > this.maxPasswordTokenAgeInHours) {
throw createError.BadRequest(`Token has expired`)
}
// Remove the password token & any login token
user.passwordToken = undefined
user.loginToken = undefined
return Promise.all([
Promise.resolve(user),
cr.hash(newPassword)
])
}).then((arr) => {
const [user, json] = arr
const json = await cr.hash(newPassword)
user.passwordHash = JSON.parse(json)
return user.save()
}).then((savedUser) => {
await user.save()
res.json({})
}).catch((err) => {
} catch(err) {
if (err instanceof createError.HttpError) {
next(err)
} else {
next(createError.InternalServerError(`Unable to confirm password reset token. ${err.message}`))
next(createError.InternalServerError(err.message))
}
})
}
}
async changePassword(req, res, next) {