You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
5.6 KiB
JavaScript
258 lines
5.6 KiB
JavaScript
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.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
|
|
})
|