UNPKG

@coko/server

Version:

Reusable server for use by Coko's projects

274 lines 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bcrypt_1 = __importDefault(require("bcrypt")); const errors_1 = require("../../errors"); const logger_1 = __importDefault(require("../../logger")); const base_model_1 = __importDefault(require("../base.model")); const useTransaction_1 = __importDefault(require("../useTransaction")); const identity_model_1 = __importDefault(require("../identity/identity.model")); const team_model_1 = __importDefault(require("../team/team.model")); const teamMember_model_1 = __importDefault(require("../teamMember/teamMember.model")); const types_1 = require("../_helpers/types"); const BCRYPT_COST = process.env.NODE_ENV === 'test' ? 1 : 12; class User extends base_model_1.default { agreedTc; givenNames; invitationToken; invitationTokenTimestamp; isActive; password; passwordHash; passwordResetTimestamp; passwordResetToken; surname; titlePost; titlePre; username; defaultIdentity; identities; teams; constructor() { super(); this.type = 'user'; } static get tableName() { return 'users'; } // Username & password are not required to allow for scenarios where a user // has been created (eg. reviewer invitation), but they have not signed up yet. static get schema() { return { type: 'object', required: [], properties: { username: types_1.alphaNumericStringNotNullable, passwordHash: types_1.stringNotEmpty, passwordResetToken: types_1.stringNullable, passwordResetTimestamp: types_1.dateNullable, agreedTc: types_1.booleanDefaultFalse, isActive: types_1.booleanDefaultFalse, invitationToken: types_1.stringNotEmpty, invitationTokenTimestamp: types_1.dateNullable, password: types_1.password, givenNames: types_1.string, surname: types_1.string, titlePre: types_1.string, titlePost: types_1.string, }, }; } static get relationMappings() { return { identities: { relation: base_model_1.default.HasManyRelation, modelClass: identity_model_1.default, join: { from: 'users.id', to: 'identities.userId', }, }, defaultIdentity: { relation: base_model_1.default.HasOneRelation, modelClass: identity_model_1.default, join: { from: 'users.id', to: 'identities.userId', }, filter: (builder) => { builder.where('isDefault', true); }, }, teams: { relation: base_model_1.default.ManyToManyRelation, modelClass: team_model_1.default, join: { from: 'users.id', through: { modelClass: teamMember_model_1.default, from: 'team_members.user_id', to: 'team_members.team_id', }, to: 'teams.id', }, }, }; } async patch(data, options = {}) { const { password: providedPassword, passwordHash } = data; if (!providedPassword && !passwordHash) { return super.patch(data, options); } throw new Error('if you want to change user password you should use updatePassword method'); } static async patchAndFetchById(id, data, options = {}) { const { password: providedPassword, passwordHash } = data; if (!providedPassword && !passwordHash) { return super.patchAndFetchById(id, data, options); } throw new Error('if you want to change user password you should use updatePassword method'); } async update(data, options = {}) { const { password: providedPassword, passwordHash } = data; if (!providedPassword && !passwordHash) { return super.update(data, options); } throw new Error('if you want to change user password you should use updatePassword method'); } static async updateAndFetchById(id, data, options = {}) { const { password: providedPassword, passwordHash } = data; if (!providedPassword && !passwordHash) { return super.updateAndFetchById(id, data, options); } throw new Error('if you want to change user password you should use updatePassword method'); } // From https://gitlab.coko.foundation/ncbi/ncbi/-/blob/develop/server/models/user/user.js#L61-101 static async hasGlobalRole(userId, role, options = {}) { try { return (0, useTransaction_1.default)(async (tr) => { const isMember = await teamMember_model_1.default.query(tr) .leftJoin('teams', 'team_members.teamId', 'teams.id') .findOne({ global: true, role, userId, }); return !!isMember; }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error('User model: hasGlobalRole failed', e); throw e; } } async hasGlobalRole(role, options = {}) { return User.hasGlobalRole(this.id, role, options); } static async hasRoleOnObject(userId, role, objectId, options = {}) { try { return (0, useTransaction_1.default)(async (tr) => { const isMember = await teamMember_model_1.default.query(tr) .leftJoin('teams', 'team_members.teamId', 'teams.id') .findOne({ role, userId, objectId, }); return !!isMember; }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error('User model: hasRoleOnObject failed', e); throw e; } } async hasRoleOnObject(role, objectId, options = {}) { return User.hasRoleOnObject(this.id, role, objectId, options); } static async getTeams(userId, options = {}) { try { const userWithTeams = await User.query(options.trx) .findById(userId) .withGraphFetched('teams') .throwIfNotFound(); return userWithTeams.teams; } catch (e) { logger_1.default.error(`User model: getTeams: ${e.message}`); throw e; } } async getTeams() { return User.getTeams(this.id); } static async updatePassword(userId, currentPassword, newPassword, passwordResetToken, options = {}) { try { return (0, useTransaction_1.default)(async (tr) => { const user = await User.findById(userId, { trx: tr, related: 'defaultIdentity', }); if (currentPassword && !passwordResetToken) { if (!(await user.isPasswordValid(currentPassword))) { throw new errors_1.ValidationError('Update password: Current password is not valid'); } } else if (user.passwordResetToken !== passwordResetToken) { throw new errors_1.ValidationError('Update password: passwordResetToken is not valid'); } if (await user.isPasswordValid(newPassword)) { throw new errors_1.ValidationError('Update password: New password must be different from current password'); } return user.$query(tr).patchAndFetch({ password: newPassword, }); }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error('User model: updatePassword failed', e); throw new Error('User model: Cannot update password'); } } async updatePassword(currentPassword, newPassword, passwordResetToken, options = {}) { return User.updatePassword(this.id, currentPassword, newPassword, passwordResetToken, options); } $formatJson(json) { json = super.$formatJson(json); delete json.passwordHash; return json; } static async hashPassword(plaintext) { return await bcrypt_1.default.hash(plaintext, BCRYPT_COST); } async hashPassword(plaintext) { this.passwordHash = await bcrypt_1.default.hash(plaintext, BCRYPT_COST); delete this.password; } async $beforeInsert() { if (this.password) await this.hashPassword(this.password); super.$beforeInsert(); } async $beforeUpdate() { if (this.password) await this.hashPassword(this.password); super.$beforeUpdate(); } async isPasswordValid(plaintext) { if (!plaintext || !this.passwordHash) return false; return await bcrypt_1.default.compare(plaintext, this.passwordHash); } static async activateUsers(ids, options = {}) { try { return (0, useTransaction_1.default)(async (tr) => { return User.query(tr) .patch({ isActive: true }) .whereIn('id', ids) .returning('*'); }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error('User model: activateUsers failed', e); throw new Error('User model: Cannot update isActive'); } } static async deactivateUsers(ids, options = {}) { try { return (0, useTransaction_1.default)(async (tr) => { return User.query(tr) .patch({ isActive: false }) .whereIn('id', ids) .returning('*'); }, { trx: options.trx, passedTrxOnly: true }); } catch (e) { logger_1.default.error('User model: deactivateUsers failed', e); throw new Error('User model: Cannot update isActive'); } } } exports.default = User; //# sourceMappingURL=user.model.js.map