import nodemailer from 'nodemailer' ; import { errors } from 'pg-promise' ; import url , { URL } from 'url' ; import db from '../../db' ; import { validatePassword } from '../validation' ; import { transporter } from './email' ; import * as userService from './user' ; param email param siteOrigin async function sendPasswordResetToken ( email , siteOrigin ) { const user = await userService . getUserByEmail ( email ) ; if ( user == undefined ) return ; const token = await createPasswordResetToken ( user . user_id ) ; const message = getPasswordResetEmail ( email , token , siteOrigin ) ; await transporter . sendMail ( message ) ; } async function resetPassword ( passwordResetToken , newPassword ) { validatePassword ( newPassword ) ; const userId = await verifyPasswordResetToken ( passwordResetToken ) ; if ( userId != undefined ) { await updatePasswordForUser ( userId , newPassword ) ; } else { throw new TokenVerificationError ( 'Password reset token could not be verified' ) ; } } class TokenVerificationError extends Error { constructor ( message ) { super ( message ) ; this . name = "TokenVerificationError" ; } } function getPasswordResetEmail ( email , token , siteOrigin ) { const linkUrl = new URL ( siteOrigin ) ; linkUrl . pathname = '/password-reset.html' ; linkUrl . search = `?token=${ token }` ; const linkString = url . format ( linkUrl ) ; const messageBody = `Hi there,

    Someone has requested a password reset for the Colouring London account associated with this email address.
    Click on the following link within the next 24 hours to reset your password:

    ${ linkString }
    ` ; return { text : messageBody , subject : 'Reset your Colouring London password' , to : email , from : 'no-reply@colouring.london' } ; } async function createPasswordResetToken ( userId ) { const { token } = await db . one ( `INSERT INTO
            user_password_reset_tokens (user_id, expires_on)
        VALUES
            ($1::uuid, now() at time zone 'utc' + INTERVAL '$2 day')
        RETURNING
            token
        ` , [ userId , 1 ] ) ; return token ; } param token returns async function verifyPasswordResetToken ( token ) { try { const usedToken = await db . one ( `UPDATE
                user_password_reset_tokens
            SET used = true
            WHERE
            token = $1::uuid
            AND NOT used
            AND now() at time zone 'utc' < expires_on
            RETURNING *` , [ token ] ) ; console . log ( 'verified' ) ; return usedToken . user_id ; } catch ( err ) { if ( err instanceof errors . QueryResultError ) { console . log ( err . code ) ; if ( err . code === errors . queryResultErrorCode . noData ) return undefined ; } throw err ; } } async function updatePasswordForUser ( userId , newPassword ) { return db . none ( `UPDATE
            users
        SET
            pass = crypt($1, gen_salt('bf'))
        WHERE
            user_id = $2::uuid
        ` , [ newPassword , userId ] ) ; } export { sendPasswordResetToken , resetPassword , TokenVerificationError } ;