Create send forgot email, route for reset on UI and reset APIs, resetting password.

This commit is contained in:
Bradley Shellnut 2021-04-26 15:30:14 -07:00
parent 1f9d1531a2
commit d480043b5a
3 changed files with 122 additions and 0 deletions

77
api/src/accounts/reset.js Normal file
View file

@ -0,0 +1,77 @@
import crypto from 'crypto'
const { ROOT_DOMAIN, JWT_SIGNATURE } = process.env
function createResetToken(email, expTimestamp) {
try {
// Auth String, JWT Signature, email, expTimestamp
const authString = `${JWT_SIGNATURE}:${email}:${expTimestamp}`
return crypto.createHash('sha256').update(authString).digest('hex');
} catch (e) {
console.log('e', e);
}
}
function validateExpTimestamp(expTimestamp) {
// One day milliseconds
const expTime = 24 * 60 * 60 * 1000
// Difference between now and expired time
const dateDiff = Number(expTimestamp) - Date.now()
// We're expired if not past OR difference in time is less than allowed
const isValid = dateDiff > 0 && dateDiff < expTime
return isValid
}
export async function createResetEmailLink(email) {
try {
// Encode url string
const URIencodedEmail = encodeURIComponent(email);
// Create timestamp
const expTimestamp = Date.now() + 24 * 60 * 60 * 1000
// Create token
const token = createResetToken(email, expTimestamp)
// Link email contains user email, token, expiration date
return `https://${ROOT_DOMAIN}/reset/${URIencodedEmail}/${expTimestamp}/${token}`
} catch (e) {
console.log('e', e);
}
}
export async function createResetLink(email) {
try {
const { user } = await import ("../user/user.js")
// Check to see if a user exists with that email
const foundUser = await user.findOne({
'email.address': email,
})
// If user exists
if (foundUser) {
// Create email link
const link = await createResetEmailLink(email)
return link
}
return ''
} catch (e) {
console.log('e', e);
return false
}
}
export async function validateResetEmail(token, email, expTimestamp) {
try {
// Create a hash aka token
const resetToken = createResetToken(email, expTimestamp)
// Compare hash with token
const isValid = resetToken === token
// Time is not expired
const isTimestampValid = validateExpTimestamp(expTimestamp)
return isValid && isTimestampValid
} catch (e) {
console.log('e', e)
return false
}
}

View file

@ -13,6 +13,7 @@ import { logUserOut } from './accounts/logUserOut.js'
import { getUserFromCookies, changePassword } from './accounts/user.js'
import { sendEmail, mailInit } from './mail/index.js'
import { createVerifyEmailLink, validateVerifyEmail } from './accounts/verify.js'
import { createResetLink, validateResetEmail } from './accounts/reset.js'
// ESM specific "features"
const __filename = fileURLToPath(import.meta.url)
@ -115,6 +116,48 @@ async function startApp() {
}
})
app.post('/api/forgot-password', {}, async (request, reply) => {
try {
const { email } = request.body
const link = await createResetLink(email)
// Send email with link
if (link) {
await sendEmail({
to: email,
subject: "Reset your password",
html: /*html*/ `<a href="${link}">Reset</a>`,
})
}
return reply.code(200).send()
} catch (e) {
console.error('e', e);
return reply.code(401).send()
}
})
app.post('/api/reset', {}, async (request, reply) => {
try {
const { email, password, token, time } = request.body
const isValid = await validateResetEmail(token, email, time)
if (isValid) {
// Find User
const { user } = await import('./user/user.js')
const foundUser = await user.findOne({
"email.address": email,
})
// Change password
if (foundUser._id) {
await changePassword(foundUser._id, password)
return reply.code(200).send('Password Updated')
}
}
return reply.code(401).send('Reset failed')
} catch (e) {
console.error('e', e);
return reply.code(401).send()
}
})
app.post('/api/verify', {}, async (request, reply) => {
try {
const { token, email } = request.body

View file

@ -17,6 +17,8 @@ async function startApp() {
root: path.join(__dirname, "public"),
})
app.get('/reset/:email/:exp/:token', {}, async (request, reply) => reply.sendFile('reset.html'))
app.get('/verify/:email/:token', {}, async ( request, reply ) => {
try {
const { email, token } = request.params