mirror of
https://github.com/BradNut/node-auth
synced 2025-09-08 17:40:17 +00:00
Create send forgot email, route for reset on UI and reset APIs, resetting password.
This commit is contained in:
parent
1f9d1531a2
commit
d480043b5a
3 changed files with 122 additions and 0 deletions
77
api/src/accounts/reset.js
Normal file
77
api/src/accounts/reset.js
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue