UNPKG

@coko/server

Version:

Reusable server for use by Coko's projects

454 lines 20.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyEmail = exports.signUp = exports.sendPasswordResetEmail = exports.setDefaultIdentity = exports.resendVerificationEmailAfterLogin = exports.resendVerificationEmail = exports.resetPassword = exports.updatePassword = exports.updateUser = exports.login = exports.getUserTeams = exports.getUsers = exports.getUser = exports.getDisplayName = exports.deleteUsers = exports.deleteUser = exports.deactivateUsers = exports.deactivateUser = exports.activateUsers = exports.activateUser = void 0; const crypto_1 = __importDefault(require("crypto")); const dayjs_1 = __importDefault(require("dayjs")); const errors_1 = require("../../errors"); const logger_1 = __importDefault(require("../../logger")); const authentication_1 = __importDefault(require("../../authentication")); const user_model_1 = __importDefault(require("./user.model")); const identity_model_1 = __importDefault(require("../identity/identity.model")); const useTransaction_1 = __importDefault(require("../useTransaction")); const emailTemplates_1 = require("../_helpers/emailTemplates"); const notify_1 = __importDefault(require("../../services/notify")); const constants_1 = require("../../services/constants"); const constants_2 = require("./constants"); const createJWT = authentication_1.default.token.create; const { EMAIL } = constants_1.notificationTypes; const { USER_CONTROLLER } = constants_2.labels; const activateUser = async (id, options = {}) => { try { return (0, useTransaction_1.default)(async (tr) => { logger_1.default.info(`${USER_CONTROLLER} activateUser: activating user with id ${id}`); const queryRes = await user_model_1.default.activateUsers([id], { trx: tr }); return queryRes[0]; }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} activateUser: ${e.message}`); throw e; } }; exports.activateUser = activateUser; const activateUsers = async (ids, options = {}) => { try { return (0, useTransaction_1.default)(async (tr) => { logger_1.default.info(`${USER_CONTROLLER} activateUsers: activating users with ids ${ids}`); return user_model_1.default.activateUsers(ids, { trx: tr }); }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} activateUsers: ${e.message}`); throw e; } }; exports.activateUsers = activateUsers; const getUser = async (id, options = {}) => { try { const { trx, ...restOptions } = options; return (0, useTransaction_1.default)(async (tr) => { logger_1.default.info(`${USER_CONTROLLER} getUser: fetching user with id ${id}`); return user_model_1.default.findById(id, { trx: tr, ...restOptions }); }, { trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} getUser: ${e.message}`); throw e; } }; exports.getUser = getUser; const getDisplayName = (user) => { const { givenNames, surname, username } = user; if (givenNames && surname) return `${givenNames} ${surname}`; if (username) return username; throw new Error(`${USER_CONTROLLER} getDisplayName: Cannot get displayName`); }; exports.getDisplayName = getDisplayName; const getUsers = async (queryParams = {}, options = {}) => { try { const { trx, ...restOptions } = options; return (0, useTransaction_1.default)(async (tr) => { logger_1.default.info(`${USER_CONTROLLER} getUsers: fetching all users based on provided options ${restOptions}`); return user_model_1.default.find(queryParams, { trx: tr, ...restOptions }); }, { trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} getUsers: ${e.message}`); throw e; } }; exports.getUsers = getUsers; const deleteUser = async (id, options = {}) => { return user_model_1.default.deleteById(id, { trx: options.trx }); }; exports.deleteUser = deleteUser; const deleteUsers = async (ids, options = {}) => { return user_model_1.default.deleteByIds(ids, { trx: options.trx }); }; exports.deleteUsers = deleteUsers; const deactivateUser = async (id, options = {}) => { try { return (0, useTransaction_1.default)(async (tr) => { logger_1.default.info(`${USER_CONTROLLER} deactivateUser: deactivating user with id ${id}`); const queryRes = await user_model_1.default.deactivateUsers([id], { trx: tr }); return queryRes[0]; }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} deactivateUser: ${e.message}`); throw e; } }; exports.deactivateUser = deactivateUser; const deactivateUsers = async (ids, options = {}) => { try { return (0, useTransaction_1.default)(async (tr) => { logger_1.default.info(`${USER_CONTROLLER} deactivateUsers: deactivating users with id ${ids}`); return user_model_1.default.deactivateUsers(ids, { trx: tr }); }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} deactivateUsers: ${e.message}`); throw e; } }; exports.deactivateUsers = deactivateUsers; const updateUser = async (id, data, options = {}) => { try { const { email, identityId, ...restData } = data; const { trx, ...restOptions } = options; logger_1.default.info(`${USER_CONTROLLER} updateUser: updating user with id ${id}`); return (0, useTransaction_1.default)(async (tr) => { if (!email) { return user_model_1.default.patchAndFetchById(id, { ...restData }, { trx: tr, ...restOptions, }); } logger_1.default.info(`${USER_CONTROLLER} updateUser: updating user identity with provided email`); if (!identityId) { throw new Error(`${USER_CONTROLLER} updateUser: cannot update email without identity id`); } await identity_model_1.default.patchAndFetchById(identityId, { email }, { trx: tr }); if (Object.keys(restData).length !== 0) { logger_1.default.info(`${USER_CONTROLLER} updateUser: updating user with provided info`); return user_model_1.default.patchAndFetchById(id, restData, { trx: tr, ...restOptions, }); } return user_model_1.default.findById(id, { trx: tr, ...restOptions, }); }, { trx }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} updateUser: ${e.message}`); throw e; } }; exports.updateUser = updateUser; const login = async (input) => { try { let isValid = false; let user; const { username, email, password } = input; if (!username) { logger_1.default.info(`${USER_CONTROLLER} login: searching for user with email ${email}`); const identity = await identity_model_1.default.findOne({ email }); if (!identity) throw new errors_1.AuthenticationError('Wrong username or password.'); user = await user_model_1.default.findById(identity.userId); } else { logger_1.default.info(`${USER_CONTROLLER} login: searching for user with username ${username}`); user = await user_model_1.default.findOne({ username }); if (!user) throw new errors_1.AuthenticationError('Wrong username or password.'); } logger_1.default.info(`${USER_CONTROLLER} login: checking password validity for user with id ${user.id}`); isValid = await user.isPasswordValid(password); if (!isValid) { throw new errors_1.AuthenticationError('Wrong username or password.'); } logger_1.default.info(`${USER_CONTROLLER} login: password is valid`); return { user, token: createJWT(user), }; } catch (e) { logger_1.default.error(`${USER_CONTROLLER} login: ${e.message}`); throw e; } }; exports.login = login; const signUp = async (data, options = {}) => { try { const { email, ...restData } = data; return (0, useTransaction_1.default)(async (tr) => { if (restData.username) { const usernameExists = await user_model_1.default.findOne({ username: restData.username }, { trx: tr }); if (usernameExists) { logger_1.default.error(`${USER_CONTROLLER} signUp: username already exists`); throw new errors_1.ConflictError('Username already exists'); } } const existingIdentity = await identity_model_1.default.findOne({ email }, { trx: tr }); if (existingIdentity) { const user = await user_model_1.default.findById(existingIdentity.userId, { trx: tr }); if (user.agreedTc) { logger_1.default.error(`${USER_CONTROLLER} signUp: a user with this email already exists`); throw new errors_1.ConflictError('A user with this email already exists'); } // If not agreed to tc, user's been invited but is now signing up logger_1.default.info(`${USER_CONTROLLER} signUp: connecting user with identity`); const updatedUser = await user_model_1.default.patchAndFetchById(existingIdentity.userId, { ...restData, }, { trx: tr }); const verificationToken = crypto_1.default.randomBytes(64).toString('hex'); const verificationTokenTimestamp = new Date(); existingIdentity.patch({ verificationToken, verificationTokenTimestamp }, { trx: tr }); const emailData = (0, emailTemplates_1.identityVerification)({ verificationToken, email: existingIdentity.email, }); (0, notify_1.default)(EMAIL, emailData); return updatedUser.id; } logger_1.default.info(`${USER_CONTROLLER} signUp: creating user`); const newUser = await user_model_1.default.insert({ ...restData, }, { trx: tr }); const verificationToken = crypto_1.default.randomBytes(64).toString('hex'); const verificationTokenTimestamp = new Date(); logger_1.default.info(`${USER_CONTROLLER} signUp: creating user local identity with provided email`); await identity_model_1.default.insert({ userId: newUser.id, email, isSocial: false, verificationTokenTimestamp, verificationToken, isVerified: false, isDefault: true, }, { trx: tr }); const emailData = (0, emailTemplates_1.identityVerification)({ verificationToken, email, }); (0, notify_1.default)(EMAIL, emailData); return newUser.id; }, { trx: options.trx }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} signUp: ${e.message}`); throw e; } }; exports.signUp = signUp; const verifyEmail = async (token, options = {}) => { try { const { trx } = options; logger_1.default.info(`${USER_CONTROLLER} verifyEmail: verifying user email`); return (0, useTransaction_1.default)(async (tr) => { const identity = await identity_model_1.default.findOne({ verificationToken: token, }, { trx: tr }); if (!identity) throw new Error(`${USER_CONTROLLER} verifyEmail: invalid token`); if (identity.isVerified) throw new Error(`${USER_CONTROLLER} verifyEmail: identity has already been confirmed`); if (!identity.verificationTokenTimestamp) { throw new Error(`${USER_CONTROLLER} verifyEmail: confirmation token does not have a corresponding timestamp`); } if ((0, dayjs_1.default)() .subtract(24, 'hour') .isAfter(identity.verificationTokenTimestamp)) { throw new Error(`${USER_CONTROLLER} verifyEmail: Token expired`); } await identity.patch({ isVerified: true, }, { trx: tr }); await activateUser(identity.userId, { trx: tr }); return true; }, { trx }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} verifyEmail: ${e.message}`); throw e; } }; exports.verifyEmail = verifyEmail; const resendVerificationEmailCommon = async (identity, options = {}) => { const verificationToken = crypto_1.default.randomBytes(64).toString('hex'); const verificationTokenTimestamp = new Date(); await identity.patch({ verificationToken, verificationTokenTimestamp, }, { trx: options.trx }); const emailData = (0, emailTemplates_1.identityVerification)({ verificationToken, email: identity.email, }); (0, notify_1.default)(EMAIL, emailData); }; const resendVerificationEmail = async (token, options = {}) => { try { logger_1.default.info(`${USER_CONTROLLER} resendVerificationEmail: resending verification email to user`); const identity = await identity_model_1.default.findOne({ verificationToken: token, }, { trx: options.trx }); if (!identity) throw new Error(`${USER_CONTROLLER} resendVerificationEmail: Token does not correspond to an identity`); await resendVerificationEmailCommon(identity, options); return true; } catch (e) { logger_1.default.error(`${USER_CONTROLLER} resendVerificationEmail: ${e.message}`); throw e; } }; exports.resendVerificationEmail = resendVerificationEmail; const resendVerificationEmailAfterLogin = async (userId, options = {}) => { try { logger_1.default.info(`${USER_CONTROLLER} resendVerificationEmailAfterLogin: resending verification email to user`); const identity = await identity_model_1.default.findOne({ userId, isDefault: true, }, { trx: options.trx }); await resendVerificationEmailCommon(identity, options); return true; } catch (e) { logger_1.default.error(`${USER_CONTROLLER} resendVerificationEmail: ${e.message}`); throw e; } }; exports.resendVerificationEmailAfterLogin = resendVerificationEmailAfterLogin; const updatePassword = async (id, currentPassword, newPassword) => { try { logger_1.default.info(`${USER_CONTROLLER} updatePassword: updating user password`); await user_model_1.default.updatePassword(id, currentPassword, newPassword, undefined); const identity = await identity_model_1.default.findOne({ isDefault: true, userId: id }); const emailData = (0, emailTemplates_1.passwordUpdate)({ email: identity.email, }); (0, notify_1.default)(EMAIL, emailData); return true; } catch (e) { logger_1.default.error(`${USER_CONTROLLER} updatePassword: ${e.message}`); throw e; } }; exports.updatePassword = updatePassword; const sendPasswordResetEmail = async (email, options = {}) => { try { const { trx } = options; return (0, useTransaction_1.default)(async (tr) => { const identity = await identity_model_1.default.findOne({ isDefault: true, email: email.toLowerCase(), }, { trx: tr }); if (!identity) { const emailData = (0, emailTemplates_1.requestResetPasswordEmailNotFound)({ email: email.toLowerCase(), }); (0, notify_1.default)(EMAIL, emailData); return true; } const user = await user_model_1.default.findById(identity.userId, { trx: tr }); const resetToken = crypto_1.default.randomBytes(32).toString('hex'); await user.patch({ passwordResetTimestamp: new Date(), passwordResetToken: resetToken, }, { trx: tr }); logger_1.default.info(`${USER_CONTROLLER} sendPasswordResetEmail: sending password reset email`); const emailData = (0, emailTemplates_1.requestResetPassword)({ email: email.toLowerCase(), token: resetToken, }); (0, notify_1.default)(EMAIL, emailData); return true; }, { trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} sendPasswordResetEmail: ${e.message}`); throw e; } }; exports.sendPasswordResetEmail = sendPasswordResetEmail; const resetPassword = async (token, password, options = {}) => { try { const { trx } = options; logger_1.default.info(`${USER_CONTROLLER} resetPassword: resetting user password`); return (0, useTransaction_1.default)(async (tr) => { const user = await user_model_1.default.findOne({ passwordResetToken: token }, { trx: tr, related: 'defaultIdentity' }); if (!user) { throw new Error(`${USER_CONTROLLER} resetPassword: no user found with token ${token}`); } if ((0, dayjs_1.default)().subtract(24, 'hour').isAfter(user.passwordResetTimestamp)) { throw new errors_1.ValidationError(`${USER_CONTROLLER} resetPassword: your token has expired`); } await user.updatePassword(undefined, password, user.passwordResetToken, { trx: tr }); await user.patch({ passwordResetTimestamp: null, passwordResetToken: null, }, { trx: tr }); const emailData = (0, emailTemplates_1.passwordUpdate)({ email: user.defaultIdentity.email, }); (0, notify_1.default)(EMAIL, emailData); return true; }, { trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} resetPassword: ${e.message}`); throw e; } }; exports.resetPassword = resetPassword; const setDefaultIdentity = async (userId, identityId, options = {}) => { try { const { trx } = options; return (0, useTransaction_1.default)(async (tr) => { const user = await user_model_1.default.findById(userId, { trx: tr, related: 'identities', }); const { identities } = user; const previouslyDefault = identities.find(i => i.isDefault); if (previouslyDefault && previouslyDefault.id === identityId) { return user; } if (previouslyDefault) { await identity_model_1.default.patchAndFetchById(previouslyDefault.id, { isDefault: false }, { trx: tr }); } await identity_model_1.default.patchAndFetchById(identityId, { isDefault: true }, { trx: tr }); return user; }, { trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} setDefaultIdentity: ${e.message}`); throw e; } }; exports.setDefaultIdentity = setDefaultIdentity; const getUserTeams = (user) => { try { const { id } = user; return user_model_1.default.getTeams(id); } catch (e) { logger_1.default.error(`${USER_CONTROLLER} getUserTeams: ${e.message}`); throw e; } }; exports.getUserTeams = getUserTeams; //# sourceMappingURL=user.controller.js.map