@coko/server
Version:
Reusable server for use by Coko's projects
454 lines • 20.3 kB
JavaScript
;
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