Files
speckle-server/packages/server/modules/pwdreset/rest/index.js
T
2022-05-03 13:10:00 +02:00

149 lines
4.5 KiB
JavaScript

'use strict'
const crs = require('crypto-random-string')
const knex = require('@/db/knex')
const { getUserByEmail, updateUserPassword } = require('@/modules/core/services/users')
const { getServerInfo } = require('@/modules/core/services/generic')
const { sendEmail } = require('@/modules/emails')
const ResetTokens = () => knex('pwdreset_tokens')
const RefreshTokens = () => knex('refresh_tokens')
const AuthorizationCodes = () => knex('authorization_codes')
module.exports = (app) => {
// sends a password recovery email.
app.post('/auth/pwdreset/request', async (req, res) => {
try {
if (!req.body.email) throw new Error('Invalid request')
const user = await getUserByEmail({ email: req.body.email })
if (!user) throw new Error('No user with that email found.')
// check if pwd request has been already sent
const existingToken = await ResetTokens()
.select('*')
.where({ email: req.body.email })
.first()
if (existingToken) {
const timeDiff = Math.abs(Date.now() - new Date(existingToken.createdAt))
if (timeDiff / 36e5 < 1)
throw new Error(
'Password reset already requested. Please try again in 1h, or check your email for the instructions we have sent.'
)
}
// delete any previous pwd requests
await ResetTokens().where({ email: req.body.email }).del()
// create a new token
const token = {
id: crs({ length: 10 }),
email: req.body.email
}
await ResetTokens().insert(token)
const serverInfo = await getServerInfo()
// send the reset link email
const resetLink = new URL(
`/authn/resetpassword/finalize?t=${token.id}`,
process.env.CANONICAL_URL
)
const emailText = `
Hi ${user.name},
You've requested a password reset for your Speckle account at ${process.env.CANONICAL_URL}. If this wasn't you, ignore this email; otherwise, follow the link below to reset your password:
${resetLink}
The link above is valid for one hour only. If you continue to have problems, please get in touch!
Warm regards,
Speckle
`
const emailHtml = `
Hi ${user.name},
<br>
<br>
You've requested a password reset for your Speckle account. If this wasn't you, you can safely ignore this email. Otherwise, click <b><a href="${resetLink}" rel="notrack">here to reset your password</a></b>.
The link is <b>valid for one hour</b> only.
<br>
<br>
If you continue to have problems, please get in touch!
<br>
<br>
Warm regards,
<br>
Speckle
<br>
<br>
<img src="https://speckle.systems/content/images/2021/02/logo_big-1.png" style="width:30px; height:30px;">
<br>
<br>
<caption style="size:8px; color:#7F7F7F; width:400px; text-align: left;">
This email was sent from ${serverInfo.name} at ${
process.env.CANONICAL_URL
}, deployed and managed by ${serverInfo.company}. Your admin contact is ${
serverInfo.adminContact ? serverInfo.adminContact : '[not provided]'
}.
</caption>
`
await sendEmail({
to: user.email,
subject: 'Speckle Account Password Reset',
text: emailText,
html: emailHtml
})
return res.status(200).send('Password reset email sent.')
} catch (e) {
res.status(400).send(e.message)
}
})
// Finalizes password recovery.
app.post('/auth/pwdreset/finalize', async (req, res) => {
try {
if (!req.body.tokenId || !req.body.password) throw new Error('Invalid request.')
const token = await ResetTokens()
.where({ id: req.body.tokenId })
.select('*')
.first()
if (!token) throw new Error('Invalid request.')
const timeDiff = Math.abs(Date.now() - new Date(token.createdAt))
if (timeDiff / 36e5 > 1) {
throw new Error('Link expired.')
}
const user = await getUserByEmail({ email: token.email })
await updateUserPassword({ id: user.id, newPassword: req.body.password })
await ResetTokens().where({ id: req.body.tokenId }).del()
// Delete existing auth tokens
await RefreshTokens().where({ userId: user.id }).del()
await AuthorizationCodes().where({ userId: user.id }).del()
await knex.raw(
`
DELETE FROM api_tokens
WHERE owner = ?
AND id NOT IN (
SELECT p."tokenId" FROM personal_api_tokens p WHERE p."userId" = ?
)
`,
[user.id, user.id]
)
return res.status(200).send('Password reset. Please log in.')
} catch (e) {
res.status(400).send(e.message)
}
})
}