const pg = require('../pg') const joinjs = require('join-js').default; const debug = require('debug')('sos:db:user') const mappings = require('../mappings') const dbUtil = require('../util') const uuid = require('uuid') const bcrypt = require('bcrypt') const session = require('./session') const user = module.exports = {} const saltRounds = parseInt(process.env.PW_SALTROUNDS, 10) || 10 user.findById = async (user_uuid) => { const query = { text: 'select * from "user" where user_uuid = $1', values: [ user_uuid ] } debug(query); const {rows} = await pg.query(query) return joinjs.map(rows, mappings, 'userMap', 'user_')[0]; } user.findByEmail = async (email) => { const query = { text: 'select * from sos.v_user where user_email = $1', values: [ email ] } debug(query); const {rows} = await pg.query(query) return joinjs.map(rows, mappings, 'userMap', 'user_')[0] } user.register = async (email, password) => { const hash = await bcrypt.hash(password, saltRounds) const query = { text: 'select * from sos.register_user($1, $2)', values: [ email, hash ] } debug(query); const {rows} = await pg.query(query) return joinjs.map(rows, mappings, 'userMap', 'user_')[0]; } user.import = async (email, hash) => { const query = { text: 'select * from sos.register_user($1, $2)', values: [ email, hash ] } debug(query); const {rows} = await pg.query(query) return joinjs.map(rows, mappings, 'userMap', 'user_')[0]; } user.updateRegistrationDate = async (uuid, timestamp, emailConfirmed) => { await dbUtil.executeQuery({ query: { text: ` update sos."user" set ( user_time_registered, user_time_password_changed ) = ( $2, $2 ) where user_uuid = $1 `, values: [uuid, timestamp] }, returnType: 'user', tablePrefix: 'user_', single: true }) if (!emailConfirmed) return; await dbUtil.executeQuery({ query: { text: ` update sos."user" set user_time_email_confirmed = $2 where user_uuid = $1 `, values: [uuid, timestamp] }, returnType: 'user', tablePrefix: 'user_', single: true }) } user.login = async (email, password) => { const _user = await user.findByEmail(email) if(!_user){ // Avoid early exit timing difference await bcrypt.hash(password, saltRounds) return null } const passwordCorrect = await bcrypt.compare(password, _user.password_hash) if(!passwordCorrect) return null return _user } user.changePassword = async (user_uuid, oldPassword, newPassword) => { const _user = await user.findById(user_uuid) if(!_user){ // Avoid early exit timing difference await bcrypt.hash(oldPassword, saltRounds) return null } const passwordCorrect = await bcrypt.compare(oldPassword, _user.password_hash) if(!passwordCorrect) return null return await user.overwritePassword(user_uuid, newPassword) } user.overwritePassword = async (user_uuid, new_password) => { const newHash = await bcrypt.hash(new_password, saltRounds) const query = { text: 'select * from sos.change_password($1, $2)', values: [ user_uuid, newHash ] } debug(query); const {rows} = await pg.query(query) return joinjs.map(rows, mappings, 'userMap', 'user_')[0]; } user.getOpenEmailLinks = (user_uuid) => dbUtil.executeFunction({ name: 'get_open_email_links_for_user', params: [ user_uuid ], returnType: 'emailLink', tablePrefix: 'email_link_', single: false }) user.createLoginLink = async (user_uuid) => { const linkCode = uuid.v4() const hash = await bcrypt.hash(linkCode, saltRounds) const link_record = await dbUtil.executeFunction({ name: 'create_login_link', params: [ user_uuid, '2 hours', hash, 'email_confirm' ], returnType: 'emailLink', tablePrefix: 'email_link_', single: true }) return `${process.env.EXTERNAL_URL}/api/email/confirm/${link_record.uuid}?key=${linkCode}` } user.verifyLoginLink = async (link_uuid, key) => { const link_record = await dbUtil.executeQuery({ query: { text: 'select * from sos.email_link where email_link_uuid = $1 and email_link_time_used is null', values: [link_uuid] }, returnType: 'emailLink', tablePrefix: 'email_link_', single: true }) if(!link_record){ // Avoid early exit timing difference await bcrypt.hash(key, saltRounds) return null } const valid = await bcrypt.compare(key, link_record.login_hash) if(!valid) return null if(link_record.type !== 'email_confirm') return null return link_record } user.createPasswordReset = async user_uuid => { const linkCode = uuid.v4() const hash = await bcrypt.hash(linkCode, saltRounds) const link_record = await dbUtil.executeFunction({ name: 'create_login_link', params: [ user_uuid, '2 hours', hash, 'password_reset' ], returnType: 'emailLink', tablePrefix: 'email_link_', single: true }) return `${process.env.EXTERNAL_URL}/account/reset-password?id=${link_record.uuid}&key=${linkCode}` } user.verifyPasswordReset = async (link_uuid, key) => { const link_record = await dbUtil.executeQuery({ query: { text: 'select * from sos.email_link where email_link_uuid = $1 and email_link_time_used is null', values: [link_uuid] }, returnType: 'emailLink', tablePrefix: 'email_link_', single: true }) if(!link_record){ // Avoid early exit timing difference await bcrypt.hash(key, saltRounds) return null } const valid = await bcrypt.compare(key, link_record.login_hash) if(!valid) return null if(link_record.type !== 'password_reset') return null return user.findById(link_record.user_uuid) } user.markLinkUsed = link_uuid => dbUtil.executeFunction({ name: 'set_link_used', params: [link_uuid], returnType: 'emailLink', tablePrefix: 'email_link_', single: true }) user.markEmailVerified = user_uuid => dbUtil.executeFunction({ name: 'set_user_email_verified', params: [user_uuid], returnType: 'user', single: true }) user.findAll = () => dbUtil.executeQuery({ query: 'select * from sos.v_user', returnType: 'user' }) user.makeAdmin = user_uuid => dbUtil.executeFunction({ name: 'set_user_admin', params: [ user_uuid, true ], returnType: 'user', single: true }) user.removeAdmin = user_uuid => dbUtil.executeFunction({ name: 'set_user_admin', params: [ user_uuid, false ], returnType: 'user', single: true })