UNPKG

@sync-in/server

Version:

The secure, open-source platform for file storage, sharing, collaboration, and sync

424 lines (423 loc) 21.9 kB
/* * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com> * This file is part of Sync-in | The open source file sync and share solution * See the LICENSE file for licensing details */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "UsersManager", { enumerable: true, get: function() { return UsersManager; } }); const _common = require("@nestjs/common"); const _bcryptjs = /*#__PURE__*/ _interop_require_default(require("bcryptjs")); const _nodefs = require("node:fs"); const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path")); const _promises = require("node:stream/promises"); const _functions = require("../../../common/functions"); const _image = require("../../../common/image"); const _configconstants = require("../../../configuration/config.constants"); const _files = require("../../files/utils/files"); const _member = require("../constants/member"); const _user = require("../constants/user"); const _usermodel = require("../models/user.model"); const _adminusersmanagerservice = require("./admin-users-manager.service"); const _usersqueriesservice = require("./users-queries.service"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _ts_decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function _ts_metadata(k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); } let UsersManager = class UsersManager { async fromUserId(id) { const user = await this.usersQueries.from(id); return user ? new _usermodel.UserModel(user, true) : null; } async findUser(loginOrEmail, removePassword = true) { const user = await this.usersQueries.from(null, loginOrEmail); return user ? new _usermodel.UserModel(user, removePassword) : null; } async logUser(user, password, ip) { if (user.role === _user.USER_ROLE.LINK) { this.logger.error(`${this.logUser.name} - guest link account ${user} is not authorized to login`); throw new _common.HttpException('Account is not allowed', _common.HttpStatus.FORBIDDEN); } if (!user.isActive || user.passwordAttempts >= _user.USER_MAX_PASSWORD_ATTEMPTS) { this.updateAccesses(user, ip, false).catch((e)=>this.logger.error(`${this.logUser.name} - ${e}`)); this.logger.error(`${this.logUser.name} - user account *${user.login}* is locked`); throw new _common.HttpException('Account locked', _common.HttpStatus.FORBIDDEN); } const authSuccess = await (0, _functions.comparePassword)(password, user.password); this.updateAccesses(user, ip, authSuccess).catch((e)=>this.logger.error(`${this.logUser.name} - ${e}`)); if (authSuccess) { await user.makePaths(); return user; } this.logger.warn(`${this.logUser.name} - bad password for *${user.login}*`); return null; } async me(authUser) { const user = await this.fromUserId(authUser.id); if (!user) { throw new _common.HttpException(`User *${authUser.login} (${authUser.id}) not found`, _common.HttpStatus.NOT_FOUND); } user.impersonated = !!authUser.impersonatedFromId; user.clientId = authUser.clientId; return { user: user }; } async compareUserPassword(userId, password) { return this.usersQueries.compareUserPassword(userId, password); } async updateLanguage(user, userLanguageDto) { if (!userLanguageDto.language) userLanguageDto.language = null; if (!await this.usersQueries.updateUserOrGuest(user.id, userLanguageDto)) { throw new _common.HttpException('Unable to update language', _common.HttpStatus.INTERNAL_SERVER_ERROR); } } async updatePassword(user, userPasswordDto) { const r = await this.usersQueries.selectUserProperties(user.id, [ 'password' ]); if (!r) { throw new _common.HttpException('Unable to check password', _common.HttpStatus.NOT_FOUND); } if (!await (0, _functions.comparePassword)(userPasswordDto.oldPassword, r.password)) { throw new _common.HttpException('Password mismatch', _common.HttpStatus.BAD_REQUEST); } const hash = await _bcryptjs.default.hash(userPasswordDto.newPassword, 10); if (!await this.usersQueries.updateUserOrGuest(user.id, { password: hash })) { throw new _common.HttpException('Unable to update password', _common.HttpStatus.INTERNAL_SERVER_ERROR); } } async updateNotification(user, userNotificationDto) { if (!await this.usersQueries.updateUserOrGuest(user.id, userNotificationDto)) { throw new _common.HttpException('Unable to update notification', _common.HttpStatus.INTERNAL_SERVER_ERROR); } } async updateAvatar(req) { const part = await req.file({ limits: { fileSize: this.maxAvatarUploadSize } }); if (!part.mimetype.startsWith('image/')) { throw new _common.HttpException('Unsupported file type', _common.HttpStatus.BAD_REQUEST); } const dstPath = _nodepath.default.join(req.user.tmpPath, this.userAvatarFileName); try { await (0, _promises.pipeline)(part.file, (0, _nodefs.createWriteStream)(dstPath)); } catch (e) { this.logger.error(`${this.updateAvatar.name} - ${e}`); throw new _common.HttpException('Unable to upload avatar', _common.HttpStatus.INTERNAL_SERVER_ERROR); } if (part.file.truncated) { this.logger.warn(`${this.updateAvatar.name} - image is too large`); throw new _common.HttpException('Image is too large (5MB max)', _common.HttpStatus.PAYLOAD_TOO_LARGE); } try { await (0, _files.moveFiles)(dstPath, _nodepath.default.join(req.user.homePath, this.userAvatarFileName), true); } catch (e) { this.logger.error(`${this.updateAvatar.name} - ${e}`); throw new _common.HttpException('Unable to create avatar', _common.HttpStatus.INTERNAL_SERVER_ERROR); } } async updateAccesses(user, ip, success) { const passwordAttempts = success ? 0 : Math.min(user.passwordAttempts + 1, _user.USER_MAX_PASSWORD_ATTEMPTS); await this.usersQueries.updateUserOrGuest(user.id, { lastAccess: user.currentAccess, currentAccess: new Date(), lastIp: user.currentIp, currentIp: ip, passwordAttempts: passwordAttempts, isActive: user.isActive && passwordAttempts < _user.USER_MAX_PASSWORD_ATTEMPTS }); } async getAvatar(userLogin, generate = false, generateIsNotExists) { const avatarPath = _nodepath.default.join(_usermodel.UserModel.getHomePath(userLogin), this.userAvatarFileName); const avatarExists = await (0, _files.isPathExists)(avatarPath); if (!avatarExists && generateIsNotExists) { generate = true; } if (!generate) { return [ avatarExists ? avatarPath : this.defaultAvatarFilePath, avatarExists ? _image.pngMimeType : _image.svgMimeType ]; } if (!await (0, _files.isPathExists)(_usermodel.UserModel.getHomePath(userLogin))) { throw new _common.HttpException(`Home path for user *${userLogin}* does not exist`, _common.HttpStatus.FORBIDDEN); } const user = await this.findUser(userLogin); if (!user) { throw new _common.HttpException(`avatar not found`, _common.HttpStatus.NOT_FOUND); } const avatarFile = (0, _nodefs.createWriteStream)(avatarPath); const avatarStream = (0, _image.generateAvatar)(user.getInitials()); try { await (0, _promises.pipeline)(avatarStream, avatarFile); } catch (e) { this.logger.error(`${this.updateAvatar.name} - ${e}`); throw new _common.HttpException('Unable to create avatar', _common.HttpStatus.INTERNAL_SERVER_ERROR); } if (generateIsNotExists) { return [ avatarPath, _image.pngMimeType ]; } } async getAvatarBase64(userLogin) { const userAvatarPath = _nodepath.default.join(_usermodel.UserModel.getHomePath(userLogin), this.userAvatarFileName); return (0, _image.convertImageToBase64)(await (0, _files.isPathExists)(userAvatarPath) ? userAvatarPath : this.defaultAvatarFilePath); } setOnlineStatus(user, onlineStatus) { this.usersQueries.setOnlineStatus(user.id, onlineStatus).catch((e)=>this.logger.error(`${this.setOnlineStatus.name} - ${e}`)); } getOnlineUsers(userIds) { return this.usersQueries.getOnlineUsers(userIds); } async usersWhitelist(userId) { return this.usersQueries.usersWhitelist(userId); } async browseGroups(user, name) { if (name) { const group = await this.usersQueries.groupFromName(user.id, name); if (!group) { throw new _common.HttpException('Group not found', _common.HttpStatus.NOT_FOUND); } return { parentGroup: group, members: await this.usersQueries.browseGroupMembers(group.id) }; } return { parentGroup: undefined, members: await this.usersQueries.browseRootGroups(user.id) }; } async getGroup(user, groupId, withMembers = true, asAdmin = false) { const group = withMembers ? await this.usersQueries.getGroupWithMembers(user.id, groupId, asAdmin) : await this.usersQueries.getGroup(user.id, groupId, asAdmin); if (!group) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } return group; } async createPersonalGroup(user, userCreateOrUpdateGroupDto) { if (!userCreateOrUpdateGroupDto.name) { this.logger.error(`${this.createPersonalGroup.name} - missing group name : ${JSON.stringify(userCreateOrUpdateGroupDto)}`); throw new _common.HttpException('Group name is missing', _common.HttpStatus.BAD_REQUEST); } if (await this.usersQueries.checkGroupNameExists(userCreateOrUpdateGroupDto.name)) { throw new _common.HttpException('Name already used', _common.HttpStatus.BAD_REQUEST); } try { const groupId = await this.usersQueries.createPersonalGroup(user.id, userCreateOrUpdateGroupDto); this.logger.log(`${this.createPersonalGroup.name} - group (${groupId}) was created : ${JSON.stringify(userCreateOrUpdateGroupDto)}`); // clear user whitelists this.usersQueries.clearWhiteListCaches([ user.id ]); return this.getGroup(user, groupId, false); } catch (e) { this.logger.error(`${this.createPersonalGroup.name} - group was not created : ${JSON.stringify(userCreateOrUpdateGroupDto)} : ${e}`); throw new _common.HttpException('Unable to create group', _common.HttpStatus.INTERNAL_SERVER_ERROR); } } async updatePersonalGroup(user, groupId, userCreateOrUpdateGroupDto) { if (!Object.keys(userCreateOrUpdateGroupDto).length) { throw new _common.HttpException('No changes to update', _common.HttpStatus.BAD_REQUEST); } const currentGroup = await this.getGroup(user, groupId, false, user.isAdmin); if (currentGroup.type !== _member.MEMBER_TYPE.PGROUP) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } if (userCreateOrUpdateGroupDto.name && await this.usersQueries.checkGroupNameExists(userCreateOrUpdateGroupDto.name)) { throw new _common.HttpException('Name already used', _common.HttpStatus.BAD_REQUEST); } try { await this.usersQueries.updateGroup(groupId, userCreateOrUpdateGroupDto); } catch (e) { throw new _common.HttpException(e.message, _common.HttpStatus.INTERNAL_SERVER_ERROR); } return this.getGroup(user, groupId, false, user.isAdmin); } async addUsersToGroup(user, groupId, userIds) { const currentGroup = await this.getGroup(user, groupId); // only users can be added to users groups // guests and users can be added to personal groups const userWhiteList = await this.usersQueries.usersWhitelist(user.id, currentGroup.type === _member.MEMBER_TYPE.GROUP ? _user.USER_ROLE.USER : undefined); // ignore user ids that are already group members & filter on user ids allowed to current user userIds = userIds.filter((id)=>!currentGroup.members.find((m)=>m.id === id)).filter((id)=>userWhiteList.indexOf(id) > -1); if (!userIds.length) { throw new _common.HttpException('No users to add to group', _common.HttpStatus.BAD_REQUEST); } return this.usersQueries.updateGroupMembers(groupId, { add: userIds.map((id)=>({ id: id, groupRole: _user.USER_GROUP_ROLE.MEMBER })) }); } async updateUserFromPersonalGroup(user, groupId, userId, updateUserFromGroupDto) { const currentGroup = await this.getGroup(user, groupId); if (currentGroup.type !== _member.MEMBER_TYPE.PGROUP) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } const userToUpdate = currentGroup.members.find((m)=>m.id === userId); if (!userToUpdate) { throw new _common.HttpException('User was not found', _common.HttpStatus.BAD_REQUEST); } if (userToUpdate.groupRole !== updateUserFromGroupDto.role) { if (userToUpdate.groupRole === _user.USER_GROUP_ROLE.MANAGER) { if (currentGroup.members.filter((m)=>m.groupRole === _user.USER_GROUP_ROLE.MANAGER).length === 1) { throw new _common.HttpException('Group must have at least one manager', _common.HttpStatus.BAD_REQUEST); } } return this.adminUsersManager.updateUserFromGroup(groupId, userId, updateUserFromGroupDto); } } async removeUserFromGroup(user, groupId, userId) { const currentGroup = await this.getGroup(user, groupId); const userToRemove = currentGroup.members.find((m)=>m.id === userId); if (!userToRemove) { throw new _common.HttpException('User was not found', _common.HttpStatus.BAD_REQUEST); } if (userToRemove.groupRole === _user.USER_GROUP_ROLE.MANAGER) { if (currentGroup.type === _member.MEMBER_TYPE.GROUP) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } if (currentGroup.members.filter((m)=>m.groupRole === _user.USER_GROUP_ROLE.MANAGER).length === 1) { throw new _common.HttpException('Group must have at least one manager', _common.HttpStatus.BAD_REQUEST); } } return this.usersQueries.updateGroupMembers(groupId, { remove: [ userId ] }); } async leavePersonalGroup(user, groupId) { const currentGroup = await this.usersQueries.getGroupWithMembers(user.id, groupId, true); if (!currentGroup || currentGroup.type === _member.MEMBER_TYPE.GROUP || !currentGroup.members.find((m)=>m.id === user.id)) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } const userWhoLeaves = currentGroup.members.find((m)=>m.id === user.id); if (!userWhoLeaves) { throw new _common.HttpException('User was not found', _common.HttpStatus.BAD_REQUEST); } if (userWhoLeaves.groupRole === _user.USER_GROUP_ROLE.MANAGER) { if (currentGroup.members.filter((m)=>m.groupRole === _user.USER_GROUP_ROLE.MANAGER).length === 1) { throw new _common.HttpException('Group must have at least one manager', _common.HttpStatus.BAD_REQUEST); } } try { await this.usersQueries.updateGroupMembers(groupId, { remove: [ user.id ] }); this.logger.log(`${this.leavePersonalGroup.name} - user (${user.id}) has left group (${groupId})`); } catch (e) { this.logger.error(`${this.leavePersonalGroup.name} - user (${user.id}) has not left group (${groupId}) : ${e}`); throw new _common.HttpException(e.message, _common.HttpStatus.INTERNAL_SERVER_ERROR); } } async deletePersonalGroup(user, groupId) { if (!await this.usersQueries.canDeletePersonalGroup(user.id, groupId)) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } if (await this.usersQueries.deletePersonalGroup(groupId)) { this.logger.log(`${this.deletePersonalGroup.name} - group (${groupId}) was deleted`); } else { this.logger.warn(`${this.deletePersonalGroup.name} - group (${groupId}) does not exist`); throw new _common.HttpException('Unable to delete group', _common.HttpStatus.BAD_REQUEST); } } listGuests(user) { return this.usersQueries.listGuests(null, user.id); } async getGuest(user, guestId) { const guest = await this.usersQueries.listGuests(guestId, user.id); this.adminUsersManager.checkUser(guest, true); return guest; } async createGuest(user, createGuestDto) { // filter managers that are allowed for current user const userWhiteList = await this.usersQueries.usersWhitelist(user.id, _user.USER_ROLE.USER); createGuestDto.managers = createGuestDto.managers.filter((id)=>userWhiteList.indexOf(id) > -1); if (createGuestDto.managers.indexOf(user.id) === -1) { // force user as manager during creation createGuestDto.managers.push(user.id); } // clear user whitelists this.usersQueries.clearWhiteListCaches([ user.id ]); return this.adminUsersManager.createUserOrGuest(createGuestDto, _user.USER_ROLE.GUEST, true); } async updateGuest(user, guestId, updateGuestDto) { if (!Object.keys(updateGuestDto).length) { throw new _common.HttpException('No changes to update', _common.HttpStatus.BAD_REQUEST); } if (updateGuestDto.managers) { // filter managers that are allowed for current user const userWhiteList = await this.usersQueries.usersWhitelist(user.id, _user.USER_ROLE.USER); updateGuestDto.managers = updateGuestDto.managers.filter((id)=>userWhiteList.indexOf(id) > -1); if (!updateGuestDto.managers.length) { throw new _common.HttpException('Guest must have at least one manager', _common.HttpStatus.BAD_REQUEST); } } if (!await this.usersQueries.isGuestManager(user.id, guestId)) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } const guest = await this.adminUsersManager.updateUserOrGuest(guestId, updateGuestDto, _user.USER_ROLE.GUEST); return guest.managers.find((m)=>m.id === user.id) ? guest : null; } async deleteGuest(user, guestId) { const guest = await this.usersQueries.isGuestManager(user.id, guestId); if (!guest) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } // guest has no space but a temporary directory return this.adminUsersManager.deleteUserOrGuest(guest.id, guest.login, { deleteSpace: true, isGuest: true }); } searchMembers(user, searchMembersDto) { return this.usersQueries.searchUsersOrGroups(searchMembersDto, user.id); } constructor(usersQueries, adminUsersManager){ this.usersQueries = usersQueries; this.adminUsersManager = adminUsersManager; this.defaultAvatarFilePath = _nodepath.default.join(_configconstants.STATIC_ASSETS_PATH, 'avatar.svg'); this.userAvatarFileName = 'avatar.png'; this.maxAvatarUploadSize = 1024 * 1024 * 5; this.logger = new _common.Logger(UsersManager.name); } }; UsersManager = _ts_decorate([ (0, _common.Injectable)(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ typeof _usersqueriesservice.UsersQueries === "undefined" ? Object : _usersqueriesservice.UsersQueries, typeof _adminusersmanagerservice.AdminUsersManager === "undefined" ? Object : _adminusersmanagerservice.AdminUsersManager ]) ], UsersManager); //# sourceMappingURL=users-manager.service.js.map