import { Db , FindAndModifyWriteOpResultObject } from "mongodb" ; import { addOrUpdate , dbOperation } from "./documentdb" ; import { Person } from "./person" ; import uuid from "uuid" ; import bcrypt from "bcrypt" ; import { dateFromNow , dateFromNowNumber , getRandomBytes , log , makeStringUrlSafe , ObjectLiteral , } from "./utils" ; import { DAY } from "./constants" ; const AMOUNT_OF_BCRYPT_ROUNDS = 4096 ; const BCRYPT_SALT_ROUNDS = Math . log2 ( AMOUNT_OF_BCRYPT_ROUNDS ) ; export async function createPerson ( { email , givenName , familyName , password , } ) { const personOptions = { email , givenName , familyName , _id : uuid . v4 ( ) , created : Date . now ( ) , updated : Date . now ( ) , } ; const person = await dbOperation ( async function ( database ) { const updatedResult = await addOrUpdate ( database , "people" , personOptions ) ; const setPasswordResult = await _setPassword ( database , updatedResult as Person , password ) ; return setPasswordResult as Person ; } ) ; return person ; } export async function getPersonByEmail ( email ) { return dbOperation ( async function ( database ) { return database . collection ( "people" ) . findOne ( { email } ) ; } ) ; } export async function _makeHashedPassword ( plainTextPassword ) { return bcrypt . hash ( plainTextPassword , BCRYPT_SALT_ROUNDS ) ; } export async function _checkPassword ( plainTextPassword , hashedPassword ) { return bcrypt . compare ( plainTextPassword , hashedPassword ) ; } async function _setPassword ( database , person , suppliedPassword ) { const hashedPassword = await _makeHashedPassword ( suppliedPassword ) ; person . password = hashedPassword ; person . passwordResetToken = null ; person . passwordResetTokenExpires = null ; return addOrUpdate ( database , "people" , person ) ; } export async function setPassword ( person , suppliedPassword ) { return await dbOperation ( async function ( database ) { return await _setPassword ( database , person , suppliedPassword ) ; } ) ; } export async function resetPasswordWithToken ( suppliedResetToken , suppliedPassword ) { const now = Date . now ( ) ; return dbOperation ( async function ( database ) { const person = await database . collection ( "people" ) . findOne ( { passwordResetToken : suppliedResetToken , passwordResetTokenExpires : { $gte : now } , } ) ; if ( ! person ) { throw new Error ( "Invalid or expired token" ) ; } await _setPassword ( database , person , suppliedPassword ) ; return person ; } ) ; } export async function authenticate ( person , suggestedPassword ) { if ( ! person . password ) { log ( `No password on person ${ person . email }` ) ; return null ; } return _checkPassword ( suggestedPassword , person . password ) ; } export async function authenticateWithEmail ( email , suggestedPassword ) { const person = await getPersonByEmail ( email ) ; if ( ! person ) { log ( `Attempt to log in as person with email address '${ email }', which doesn't exist` ) ; return null ; } const isAuthenticated = await authenticate ( person , suggestedPassword ) ; if ( ! isAuthenticated ) { return null ; } log ( `Successfully authenticated as ${ email }` ) ; return person ; } export async function makePasswordResetToken ( ) { const buffer = await getRandomBytes ( 20 ) ; return makeStringUrlSafe ( buffer . toString ( "hex" ) ) ; } async function _setPasswordResetToken ( database , person , token ) { const expiryDate = dateFromNowNumber ( 1 * DAY ) ; person . passwordResetToken = token ; person . passwordResetTokenExpires = expiryDate ; return await addOrUpdate ( database , "people" , person ) ; } export async function setPasswordResetToken ( person , token ) { return dbOperation ( async function ( database ) { return _setPasswordResetToken ( database , person , token ) ; } ) ; } export async function setPasswordResetTokenForEmail ( email , token ) { return dbOperation ( async function ( database ) { const person = await database . collection ( "people" ) . findOne ( { email } ) ; if ( ! person ) { throw new Error ( `Could not find person with email '${ email }'` ) ; } return _setPasswordResetToken ( database , person , token ) ; } ) ; }