@sync-in/server 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -2
- package/package.json +10 -9
- package/server/app.bootstrap.js +4 -2
- package/server/app.bootstrap.js.map +1 -1
- package/server/applications/files/files.controller.js +12 -4
- package/server/applications/files/files.controller.js.map +1 -1
- package/server/applications/files/files.controller.spec.js +18 -4
- package/server/applications/files/files.controller.spec.js.map +1 -1
- package/server/applications/files/services/files-manager.service.js.map +1 -1
- package/server/applications/files/services/files-methods.service.js +1 -6
- package/server/applications/files/services/files-methods.service.js.map +1 -1
- package/server/applications/files/utils/doc-textify/adapters/pdf.js +5 -16
- package/server/applications/files/utils/doc-textify/adapters/pdf.js.map +1 -1
- package/server/applications/users/services/users-manager.service.js +1 -2
- package/server/applications/users/services/users-manager.service.js.map +1 -1
- package/server/applications/users/users.gateway.js +6 -0
- package/server/applications/users/users.gateway.js.map +1 -1
- package/server/common/image.js +62 -58
- package/server/common/image.js.map +1 -1
- package/static/3rdpartylicenses.txt +0 -26
- package/static/chunk-2VMSXRCB.js +12 -0
- package/static/{chunk-X5XGK6T7.js → chunk-3OHSRRKH.js} +1 -1
- package/static/{chunk-H6NE33VX.js → chunk-3R4WKOHQ.js} +1 -1
- package/static/{chunk-WLMNXRBS.js → chunk-3R74L4UU.js} +1 -1
- package/static/{chunk-ZPF2DSQV.js → chunk-5UKZLU5H.js} +1 -1
- package/static/{chunk-KFJIQIGR.js → chunk-BQV4FRM6.js} +1 -1
- package/static/{chunk-MFLIJH6T.js → chunk-CETH7UYS.js} +1 -1
- package/static/{chunk-C36MW4ME.js → chunk-DIT6W7VM.js} +9 -9
- package/static/{chunk-YW57T2PF.js → chunk-IQSKQXC3.js} +1 -1
- package/static/{chunk-QZU2S5CV.js → chunk-ITUFI2BJ.js} +1 -1
- package/static/{chunk-PGZZP5W3.js → chunk-LCTZJ537.js} +1 -1
- package/static/{chunk-LVM4QB22.js → chunk-LK2UCQJ6.js} +1 -1
- package/static/{chunk-ZTCRGJ6Y.js → chunk-LP5TBXEN.js} +1 -1
- package/static/{chunk-BIKLW4YS.js → chunk-LVSNIS5P.js} +1 -1
- package/static/{chunk-VM4YX6Q7.js → chunk-MTVSJTIW.js} +1 -1
- package/static/{chunk-V3AT2BKP.js → chunk-O6FYXVHI.js} +1 -1
- package/static/{chunk-SHIVUDP3.js → chunk-PNR6M34W.js} +1 -1
- package/static/{chunk-M3XVNQZQ.js → chunk-QMRBZHE4.js} +1 -1
- package/static/{chunk-NO2LTNW3.js → chunk-QSJRY3TF.js} +1 -1
- package/static/{chunk-373XVRXW.js → chunk-QUUIRSYT.js} +1 -1
- package/static/{chunk-UEQCWMXD.js → chunk-RFH46UW3.js} +1 -1
- package/static/{chunk-AY2SZ3G6.js → chunk-RSXHRKM5.js} +1 -1
- package/static/{chunk-UJPPR4MX.js → chunk-RV3VZJPZ.js} +1 -1
- package/static/{chunk-JSWCNGXJ.js → chunk-S7HNXVRB.js} +1 -1
- package/static/{chunk-VKK5BSLX.js → chunk-SJR5R3Y4.js} +1 -1
- package/static/{chunk-UG5DMXYO.js → chunk-V3LHHZYN.js} +1 -1
- package/static/{chunk-HAS5ZOTR.js → chunk-VJTXJ43D.js} +1 -1
- package/static/{chunk-HNQRZALS.js → chunk-VQQKMY2C.js} +1 -1
- package/static/{chunk-MSUHTBB2.js → chunk-WSSU2HXE.js} +1 -1
- package/static/{chunk-GUGNR5TF.js → chunk-XDZGW64M.js} +2 -2
- package/static/{chunk-TPYBFZS5.js → chunk-XTRDKGKG.js} +1 -1
- package/static/{chunk-YEKR5OPO.js → chunk-YLWTEC3X.js} +1 -1
- package/static/index.html +1 -1
- package/static/{main-VOL6OMJ5.js → main-4H5BJY3J.js} +2 -2
- package/static/chunk-WJW7CT6G.js +0 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/applications/users/services/users-manager.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { MultipartFile } from '@fastify/multipart'\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport bcrypt from 'bcryptjs'\nimport { WriteStream } from 'fs'\nimport { createWriteStream } from 'node:fs'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { AUTH_SCOPE } from '../../../authentication/constants/scope'\nimport { LoginResponseDto } from '../../../authentication/dto/login-response.dto'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { JwtIdentityPayload } from '../../../authentication/interfaces/jwt-payload.interface'\nimport { ACTION } from '../../../common/constants'\nimport { comparePassword, hashPassword } from '../../../common/functions'\nimport { generateAvatar, pngMimeType, svgMimeType } from '../../../common/image'\nimport { createLightSlug, genPassword } from '../../../common/shared'\nimport { configuration, serverConfig } from '../../../configuration/config.environment'\nimport { isPathExists, moveFiles } from '../../files/utils/files'\nimport { NOTIFICATION_APP, NOTIFICATION_APP_EVENT } from '../../notifications/constants/notifications'\nimport { NotificationsManager } from '../../notifications/services/notifications-manager.service'\nimport { MEMBER_TYPE } from '../constants/member'\nimport { USER_GROUP_ROLE, USER_MAX_PASSWORD_ATTEMPTS, USER_ONLINE_STATUS, USER_ROLE } from '../constants/user'\nimport type { UserCreateOrUpdateGroupDto } from '../dto/create-or-update-group.dto'\nimport type { CreateUserDto, UpdateUserDto, UpdateUserFromGroupDto } from '../dto/create-or-update-user.dto'\nimport type { SearchMembersDto } from '../dto/search-members.dto'\nimport type {\n UserAppPasswordDto,\n UserLanguageDto,\n UserNotificationDto,\n UserStorageIndexingDto,\n UserUpdatePasswordDto\n} from '../dto/user-properties.dto'\nimport type { GroupBrowse } from '../interfaces/group-browse.interface'\nimport type { GroupMember, GroupWithMembers } from '../interfaces/group-member'\nimport type { GuestUser } from '../interfaces/guest-user.interface'\nimport type { Member } from '../interfaces/member.interface'\nimport type { UserAppPassword, UserSecrets } from '../interfaces/user-secrets.interface'\nimport type { UserOnline } from '../interfaces/websocket.interface'\nimport { UserModel } from '../models/user.model'\nimport type { Group } from '../schemas/group.interface'\nimport type { UserGroup } from '../schemas/user-group.interface'\nimport type { User } from '../schemas/user.interface'\nimport { USER_AVATAR_FILE_NAME, USER_AVATAR_MAX_UPLOAD_SIZE, USER_DEFAULT_AVATAR_FILE_PATH } from '../utils/avatar'\nimport { AdminUsersManager } from './admin-users-manager.service'\nimport { UsersQueries } from './users-queries.service'\n\n@Injectable()\nexport class UsersManager {\n private readonly logger = new Logger(UsersManager.name)\n\n constructor(\n public readonly usersQueries: UsersQueries,\n private readonly adminUsersManager: AdminUsersManager,\n private readonly notificationsManager: NotificationsManager\n ) {}\n\n async fromUserId(id: number): Promise<UserModel> {\n const user: User = await this.usersQueries.from(id)\n return user ? new UserModel(user, true) : null\n }\n\n async findUser(loginOrEmail: string, removePassword: false): Promise<UserModel>\n async findUser(loginOrEmail: string, removePassword?: true): Promise<Omit<UserModel, 'password'>>\n async findUser(loginOrEmail: string, removePassword: boolean = true): Promise<Omit<UserModel, 'password'>> {\n const user: User = await this.usersQueries.from(null, loginOrEmail)\n return user ? new UserModel(user, removePassword) : null\n }\n\n async logUser(user: UserModel, password: string, ip: string, scope?: AUTH_SCOPE): Promise<UserModel> {\n this.validateUserAccess(user, ip)\n let authSuccess: boolean = await comparePassword(password, user.password)\n if (!authSuccess && scope) {\n authSuccess = await this.validateAppPassword(user, password, ip, scope)\n }\n this.updateAccesses(user, ip, authSuccess).catch((e: Error) => this.logger.error(`${this.logUser.name} - ${e}`))\n if (authSuccess) {\n await user.makePaths()\n return user\n }\n this.logger.warn(`${this.logUser.name} - bad password for *${user.login}*`)\n return null\n }\n\n validateUserAccess(user: UserModel, ip: string) {\n if (user.role === USER_ROLE.LINK) {\n this.logger.error(`${this.validateUserAccess.name} - guest link account ${user} is not authorized to login`)\n throw new HttpException('Account is not allowed', HttpStatus.FORBIDDEN)\n }\n if (!user.isActive || user.passwordAttempts >= USER_MAX_PASSWORD_ATTEMPTS) {\n this.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error(`${this.validateUserAccess.name} - ${e}`))\n this.logger.error(`${this.validateUserAccess.name} - user account *${user.login}* is locked`)\n this.notifyAccountLocked(user, ip)\n throw new HttpException('Account locked', HttpStatus.FORBIDDEN)\n }\n }\n\n async me(authUser: UserModel): Promise<Omit<LoginResponseDto, 'token'>> {\n const user = await this.fromUserId(authUser.id)\n if (!user) {\n this.logger.warn(`User *${authUser.login} (${authUser.id}) not found`)\n throw new HttpException(`User not found`, HttpStatus.NOT_FOUND)\n }\n user.impersonated = !!authUser.impersonatedFromId\n user.clientId = authUser.clientId\n return { user: user, server: serverConfig }\n }\n\n async compareUserPassword(userId: number, password: string): Promise<boolean> {\n return this.usersQueries.compareUserPassword(userId, password)\n }\n\n async updateLanguage(user: UserModel, userLanguageDto: UserLanguageDto) {\n if (!userLanguageDto.language) userLanguageDto.language = null\n if (!(await this.usersQueries.updateUserOrGuest(user.id, userLanguageDto))) {\n throw new HttpException('Unable to update language', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updatePassword(user: UserModel, userPasswordDto: UserUpdatePasswordDto) {\n const r = await this.usersQueries.selectUserProperties(user.id, ['password'])\n if (!r) {\n throw new HttpException('Unable to check password', HttpStatus.NOT_FOUND)\n }\n if (!(await comparePassword(userPasswordDto.oldPassword, r.password))) {\n throw new HttpException('Password mismatch', HttpStatus.BAD_REQUEST)\n }\n const hash = await bcrypt.hash(userPasswordDto.newPassword, 10)\n if (!(await this.usersQueries.updateUserOrGuest(user.id, { password: hash }))) {\n throw new HttpException('Unable to update password', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateNotification(user: UserModel, userNotificationDto: UserNotificationDto) {\n if (!(await this.usersQueries.updateUserOrGuest(user.id, userNotificationDto))) {\n throw new HttpException('Unable to update notification preference', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateStorageIndexing(user: UserModel, userStorageIndexingDto: UserStorageIndexingDto) {\n if (!(await this.usersQueries.updateUserOrGuest(user.id, userStorageIndexingDto))) {\n throw new HttpException('Unable to update full-text search preference', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateAvatar(req: FastifyAuthenticatedRequest) {\n const part: MultipartFile = await req.file({ limits: { fileSize: USER_AVATAR_MAX_UPLOAD_SIZE } })\n if (!part.mimetype.startsWith('image/')) {\n throw new HttpException('Unsupported file type', HttpStatus.BAD_REQUEST)\n }\n const dstPath = path.join(req.user.tmpPath, USER_AVATAR_FILE_NAME)\n try {\n await pipeline(part.file, createWriteStream(dstPath))\n } catch (e) {\n this.logger.error(`${this.updateAvatar.name} - ${e}`)\n throw new HttpException('Unable to upload avatar', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n if (part.file.truncated) {\n this.logger.warn(`${this.updateAvatar.name} - image is too large`)\n throw new HttpException('Image is too large (5MB max)', HttpStatus.PAYLOAD_TOO_LARGE)\n }\n try {\n await moveFiles(dstPath, path.join(req.user.homePath, USER_AVATAR_FILE_NAME), true)\n } catch (e) {\n this.logger.error(`${this.updateAvatar.name} - ${e}`)\n throw new HttpException('Unable to create avatar', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateSecrets(userId: number, secrets: UserSecrets) {\n const userSecrets = await this.usersQueries.getUserSecrets(userId)\n const updatedSecrets = { ...userSecrets, ...secrets }\n if (!(await this.usersQueries.updateUserOrGuest(userId, { secrets: updatedSecrets }))) {\n throw new HttpException('Unable to update secrets', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateAccesses(user: UserModel, ip: string, success: boolean, isAuthTwoFa = false) {\n let passwordAttempts: number\n if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {\n // Do not reset password attempts if the login still requires 2FA validation\n passwordAttempts = user.passwordAttempts\n } else {\n passwordAttempts = success ? 0 : Math.min(user.passwordAttempts + 1, USER_MAX_PASSWORD_ATTEMPTS)\n }\n await this.usersQueries.updateUserOrGuest(user.id, {\n lastAccess: user.currentAccess,\n currentAccess: new Date(),\n lastIp: user.currentIp,\n currentIp: ip,\n passwordAttempts: passwordAttempts,\n isActive: user.isActive && passwordAttempts < USER_MAX_PASSWORD_ATTEMPTS\n })\n }\n\n async getAvatar(userLogin: string, generate: true, generateIsNotExists?: boolean): Promise<undefined>\n async getAvatar(userLogin: string, generate?: false, generateIsNotExists?: boolean): Promise<[path: string, mime: string]>\n async getAvatar(userLogin: string, generate: boolean = false, generateIsNotExists?: boolean): Promise<[path: string, mime: string]> {\n const avatarPath = path.join(UserModel.getHomePath(userLogin), USER_AVATAR_FILE_NAME)\n const avatarExists = await isPathExists(avatarPath)\n if (!avatarExists && generateIsNotExists) {\n generate = true\n }\n if (!generate) {\n return [avatarExists ? avatarPath : USER_DEFAULT_AVATAR_FILE_PATH, avatarExists ? pngMimeType : svgMimeType]\n }\n if (!(await isPathExists(UserModel.getHomePath(userLogin)))) {\n throw new HttpException(`Home path for user *${userLogin}* does not exist`, HttpStatus.FORBIDDEN)\n }\n const user: Partial<UserModel> = await this.findUser(userLogin)\n if (!user) {\n throw new HttpException(`avatar not found`, HttpStatus.NOT_FOUND)\n }\n const avatarFile: WriteStream = createWriteStream(avatarPath)\n const avatarStream = Readable.from(await generateAvatar(user.getInitials()))\n try {\n await pipeline(avatarStream, avatarFile)\n } catch (e) {\n this.logger.error(`${this.updateAvatar.name} - ${e}`)\n throw new HttpException('Unable to create avatar', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n if (generateIsNotExists) {\n return [avatarPath, pngMimeType]\n }\n }\n\n async listAppPasswords(user: UserModel): Promise<Omit<UserAppPassword, 'password'>[]> {\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n if (Array.isArray(secrets.appPasswords)) {\n // remove passwords from response\n return secrets.appPasswords.map(({ password, ...rest }: UserAppPassword) => rest)\n }\n return []\n }\n\n async generateAppPassword(user: UserModel, userAppPasswordDto: UserAppPasswordDto): Promise<UserAppPassword> {\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n const slugName = createLightSlug(userAppPasswordDto.name)\n if (Array.isArray(secrets.appPasswords) && secrets.appPasswords.find((p: UserAppPassword) => p.name === slugName)) {\n throw new HttpException('Name already used', HttpStatus.BAD_REQUEST)\n }\n secrets.appPasswords = Array.isArray(secrets.appPasswords) ? secrets.appPasswords : []\n const clearPassword = genPassword(24)\n const appPassword: UserAppPassword = {\n name: createLightSlug(userAppPasswordDto.name),\n app: userAppPasswordDto.app,\n expiration: userAppPasswordDto.expiration,\n password: await hashPassword(clearPassword),\n createdAt: new Date(),\n currentIp: null,\n currentAccess: null,\n lastIp: null,\n lastAccess: null\n }\n secrets.appPasswords.unshift(appPassword)\n if (!(await this.usersQueries.updateUserOrGuest(user.id, { secrets: secrets }))) {\n throw new HttpException('Unable to update app passwords', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n // return clear password only once\n return { ...appPassword, password: clearPassword }\n }\n\n async deleteAppPassword(user: UserModel, passwordName: string): Promise<void> {\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n if (!Array.isArray(secrets.appPasswords) || !secrets.appPasswords.find((p: UserAppPassword) => p.name === passwordName)) {\n throw new HttpException('App password not found', HttpStatus.NOT_FOUND)\n }\n secrets.appPasswords = secrets.appPasswords.filter((p: UserAppPassword) => p.name !== passwordName)\n if (!(await this.usersQueries.updateUserOrGuest(user.id, { secrets: secrets }))) {\n throw new HttpException('Unable to delete app password', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async validateAppPassword(user: UserModel, password: string, ip: string, scope: AUTH_SCOPE): Promise<boolean> {\n if (!scope || !user.haveRole(USER_ROLE.USER)) return false\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n if (!Array.isArray(secrets.appPasswords)) return false\n for (const p of secrets.appPasswords) {\n if (p.app !== scope) continue\n const expMs = p.expiration ? new Date(p.expiration) : null\n if (p.expiration && new Date() > expMs) continue // expired\n if (await comparePassword(password, p.password)) {\n p.lastAccess = p.currentAccess\n p.currentAccess = new Date()\n p.lastIp = p.currentIp\n p.currentIp = ip\n // update accesses\n this.usersQueries\n .updateUserOrGuest(user.id, { secrets: secrets })\n .catch((e: Error) => this.logger.error(`${this.validateAppPassword.name} - ${e}`))\n return true\n }\n }\n return false\n }\n\n setOnlineStatus(user: JwtIdentityPayload, onlineStatus: USER_ONLINE_STATUS) {\n this.usersQueries.setOnlineStatus(user.id, onlineStatus).catch((e: Error) => this.logger.error(`${this.setOnlineStatus.name} - ${e}`))\n }\n\n getOnlineUsers(userIds: number[]): Promise<UserOnline[]> {\n return this.usersQueries.getOnlineUsers(userIds)\n }\n\n async usersWhitelist(userId: number): Promise<number[]> {\n return this.usersQueries.usersWhitelist(userId)\n }\n\n async browseGroups(user: UserModel, name: string): Promise<GroupBrowse> {\n if (name) {\n const group: Pick<Group, 'id' | 'name' | 'type'> & { role: UserGroup['role'] } = await this.usersQueries.groupFromName(user.id, name)\n if (!group) {\n throw new HttpException('Group not found', HttpStatus.NOT_FOUND)\n }\n return { parentGroup: group, members: await this.usersQueries.browseGroupMembers(group.id) }\n }\n return { parentGroup: undefined, members: await this.usersQueries.browseRootGroups(user.id) }\n }\n\n async getGroup(user: UserModel, groupId: number, withMembers?: true, asAdmin?: boolean): Promise<GroupWithMembers>\n async getGroup(user: UserModel, groupId: number, withMembers: false, asAdmin?: boolean): Promise<GroupMember>\n async getGroup(user: UserModel, groupId: number, withMembers = true, asAdmin = false): Promise<GroupMember | GroupWithMembers> {\n const group = withMembers\n ? await this.usersQueries.getGroupWithMembers(user.id, groupId, asAdmin)\n : await this.usersQueries.getGroup(user.id, groupId, asAdmin)\n if (!group) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n return group\n }\n\n async createPersonalGroup(user: UserModel, userCreateOrUpdateGroupDto: UserCreateOrUpdateGroupDto): Promise<GroupMember> {\n if (!userCreateOrUpdateGroupDto.name) {\n this.logger.error(`${this.createPersonalGroup.name} - missing group name : ${JSON.stringify(userCreateOrUpdateGroupDto)}`)\n throw new HttpException('Group name is missing', HttpStatus.BAD_REQUEST)\n }\n if (await this.usersQueries.checkGroupNameExists(userCreateOrUpdateGroupDto.name)) {\n throw new HttpException('Name already used', HttpStatus.BAD_REQUEST)\n }\n try {\n const groupId: number = await this.usersQueries.createPersonalGroup(user.id, userCreateOrUpdateGroupDto)\n this.logger.log(`${this.createPersonalGroup.name} - group (${groupId}) was created : ${JSON.stringify(userCreateOrUpdateGroupDto)}`)\n // clear user whitelists\n this.usersQueries.clearWhiteListCaches([user.id])\n return this.getGroup(user, groupId, false)\n } catch (e) {\n this.logger.error(`${this.createPersonalGroup.name} - group was not created : ${JSON.stringify(userCreateOrUpdateGroupDto)} : ${e}`)\n throw new HttpException('Unable to create group', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updatePersonalGroup(user: UserModel, groupId: number, userCreateOrUpdateGroupDto: UserCreateOrUpdateGroupDto): Promise<GroupMember> {\n if (!Object.keys(userCreateOrUpdateGroupDto).length) {\n throw new HttpException('No changes to update', HttpStatus.BAD_REQUEST)\n }\n const currentGroup: GroupMember = await this.getGroup(user, groupId, false, user.isAdmin)\n if (currentGroup.type !== MEMBER_TYPE.PGROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n if (userCreateOrUpdateGroupDto.name && (await this.usersQueries.checkGroupNameExists(userCreateOrUpdateGroupDto.name))) {\n throw new HttpException('Name already used', HttpStatus.BAD_REQUEST)\n }\n try {\n await this.usersQueries.updateGroup(groupId, userCreateOrUpdateGroupDto)\n } catch (e) {\n throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR)\n }\n return this.getGroup(user, groupId, false, user.isAdmin)\n }\n\n async addUsersToGroup(user: UserModel, groupId: number, userIds: number[]): Promise<void> {\n const currentGroup: GroupWithMembers = await this.getGroup(user, groupId)\n // only users can be added to users groups\n // guests and users can be added to personal groups\n const userWhiteList: number[] = await this.usersQueries.usersWhitelist(\n user.id,\n currentGroup.type === MEMBER_TYPE.GROUP ? USER_ROLE.USER : undefined\n )\n // ignore user ids that are already group members & filter on user ids allowed to current user\n userIds = userIds.filter((id) => !currentGroup.members.find((m) => m.id === id)).filter((id) => userWhiteList.indexOf(id) > -1)\n if (!userIds.length) {\n throw new HttpException('No users to add to group', HttpStatus.BAD_REQUEST)\n }\n return this.usersQueries.updateGroupMembers(groupId, { add: userIds.map((id) => ({ id: id, groupRole: USER_GROUP_ROLE.MEMBER })) })\n }\n\n async updateUserFromPersonalGroup(user: UserModel, groupId: number, userId: number, updateUserFromGroupDto: UpdateUserFromGroupDto): Promise<void> {\n const currentGroup: GroupWithMembers = await this.getGroup(user, groupId)\n if (currentGroup.type !== MEMBER_TYPE.PGROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n const userToUpdate = currentGroup.members.find((m) => m.id === userId)\n if (!userToUpdate) {\n throw new HttpException('User was not found', HttpStatus.BAD_REQUEST)\n }\n if (userToUpdate.groupRole !== updateUserFromGroupDto.role) {\n if (userToUpdate.groupRole === USER_GROUP_ROLE.MANAGER) {\n if (currentGroup.members.filter((m) => m.groupRole === USER_GROUP_ROLE.MANAGER).length === 1) {\n throw new HttpException('Group must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n return this.adminUsersManager.updateUserFromGroup(groupId, userId, updateUserFromGroupDto)\n }\n }\n\n async removeUserFromGroup(user: UserModel, groupId: number, userId: number): Promise<void> {\n const currentGroup: GroupWithMembers = await this.getGroup(user, groupId)\n const userToRemove = currentGroup.members.find((m) => m.id === userId)\n if (!userToRemove) {\n throw new HttpException('User was not found', HttpStatus.BAD_REQUEST)\n }\n if (userToRemove.groupRole === USER_GROUP_ROLE.MANAGER) {\n if (currentGroup.type === MEMBER_TYPE.GROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n if (currentGroup.members.filter((m) => m.groupRole === USER_GROUP_ROLE.MANAGER).length === 1) {\n throw new HttpException('Group must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n return this.usersQueries.updateGroupMembers(groupId, { remove: [userId] })\n }\n\n async leavePersonalGroup(user: UserModel, groupId: number): Promise<void> {\n const currentGroup: GroupWithMembers = await this.usersQueries.getGroupWithMembers(user.id, groupId, true)\n if (!currentGroup || currentGroup.type === MEMBER_TYPE.GROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n const userWhoLeaves = currentGroup.members.find((m) => m.id === user.id)\n if (!userWhoLeaves) {\n throw new HttpException('User was not found', HttpStatus.BAD_REQUEST)\n }\n if (userWhoLeaves.groupRole === USER_GROUP_ROLE.MANAGER) {\n if (currentGroup.members.filter((m) => m.groupRole === USER_GROUP_ROLE.MANAGER).length === 1) {\n throw new HttpException('Group must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n try {\n await this.usersQueries.updateGroupMembers(groupId, { remove: [user.id] })\n this.logger.log(`${this.leavePersonalGroup.name} - user (${user.id}) has left group (${groupId})`)\n } catch (e) {\n this.logger.error(`${this.leavePersonalGroup.name} - user (${user.id}) has not left group (${groupId}) : ${e}`)\n throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async deletePersonalGroup(user: UserModel, groupId: number): Promise<void> {\n if (!(await this.usersQueries.canDeletePersonalGroup(user.id, groupId))) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n if (await this.usersQueries.deletePersonalGroup(groupId)) {\n this.logger.log(`${this.deletePersonalGroup.name} - group (${groupId}) was deleted`)\n } else {\n this.logger.warn(`${this.deletePersonalGroup.name} - group (${groupId}) does not exist`)\n throw new HttpException('Unable to delete group', HttpStatus.BAD_REQUEST)\n }\n }\n\n listGuests(user: UserModel): Promise<GuestUser[]> {\n return this.usersQueries.listGuests(null, user.id)\n }\n\n async getGuest(user: UserModel, guestId: number): Promise<GuestUser> {\n const guest: GuestUser = await this.usersQueries.listGuests(guestId, user.id)\n this.adminUsersManager.checkUser(guest, true)\n return guest\n }\n\n async createGuest(user: UserModel, createGuestDto: CreateUserDto): Promise<GuestUser> {\n // filter managers that are allowed for current user\n const userWhiteList = await this.usersQueries.usersWhitelist(user.id, USER_ROLE.USER)\n createGuestDto.managers = createGuestDto.managers.filter((id) => userWhiteList.indexOf(id) > -1)\n if (createGuestDto.managers.indexOf(user.id) === -1) {\n // force user as manager during creation\n createGuestDto.managers.push(user.id)\n }\n // clear user whitelists\n this.usersQueries.clearWhiteListCaches([user.id])\n return this.adminUsersManager.createUserOrGuest(createGuestDto, USER_ROLE.GUEST, true)\n }\n\n async updateGuest(user: UserModel, guestId: number, updateGuestDto: UpdateUserDto): Promise<GuestUser> {\n if (!Object.keys(updateGuestDto).length) {\n throw new HttpException('No changes to update', HttpStatus.BAD_REQUEST)\n }\n if (updateGuestDto.managers) {\n // filter managers that are allowed for current user\n const userWhiteList = await this.usersQueries.usersWhitelist(user.id, USER_ROLE.USER)\n updateGuestDto.managers = updateGuestDto.managers.filter((id) => userWhiteList.indexOf(id) > -1)\n if (!updateGuestDto.managers.length) {\n throw new HttpException('Guest must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n if (!(await this.usersQueries.isGuestManager(user.id, guestId))) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n const guest = await this.adminUsersManager.updateUserOrGuest(guestId, updateGuestDto, USER_ROLE.GUEST)\n return guest.managers.find((m) => m.id === user.id) ? guest : null\n }\n\n async deleteGuest(user: UserModel, guestId: number): Promise<void> {\n const guest = await this.usersQueries.isGuestManager(user.id, guestId)\n if (!guest) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n // guest has no space but a temporary directory\n return this.adminUsersManager.deleteUserOrGuest(guest.id, guest.login, { deleteSpace: true, isGuest: true })\n }\n\n searchMembers(user: UserModel, searchMembersDto: SearchMembersDto): Promise<Member[]> {\n return this.usersQueries.searchUsersOrGroups(searchMembersDto, user.id)\n }\n\n private notifyAccountLocked(user: UserModel, ip: string) {\n this.notificationsManager\n .sendEmailNotification([user], {\n app: NOTIFICATION_APP.AUTH_LOCKED,\n event: NOTIFICATION_APP_EVENT.AUTH_LOCKED[ACTION.DELETE],\n element: null,\n url: ip\n })\n .catch((e: Error) => this.logger.error(`${this.validateUserAccess.name} - ${e}`))\n }\n}\n"],"names":["UsersManager","fromUserId","id","user","usersQueries","from","UserModel","findUser","loginOrEmail","removePassword","logUser","password","ip","scope","validateUserAccess","authSuccess","comparePassword","validateAppPassword","updateAccesses","catch","e","logger","error","name","makePaths","warn","login","role","USER_ROLE","LINK","HttpException","HttpStatus","FORBIDDEN","isActive","passwordAttempts","USER_MAX_PASSWORD_ATTEMPTS","notifyAccountLocked","me","authUser","NOT_FOUND","impersonated","impersonatedFromId","clientId","server","serverConfig","compareUserPassword","userId","updateLanguage","userLanguageDto","language","updateUserOrGuest","INTERNAL_SERVER_ERROR","updatePassword","userPasswordDto","r","selectUserProperties","oldPassword","BAD_REQUEST","hash","bcrypt","newPassword","updateNotification","userNotificationDto","updateStorageIndexing","userStorageIndexingDto","updateAvatar","req","part","file","limits","fileSize","USER_AVATAR_MAX_UPLOAD_SIZE","mimetype","startsWith","dstPath","path","join","tmpPath","USER_AVATAR_FILE_NAME","pipeline","createWriteStream","truncated","PAYLOAD_TOO_LARGE","moveFiles","homePath","updateSecrets","secrets","userSecrets","getUserSecrets","updatedSecrets","success","isAuthTwoFa","configuration","auth","mfa","totp","enabled","twoFaEnabled","Math","min","lastAccess","currentAccess","Date","lastIp","currentIp","getAvatar","userLogin","generate","generateIsNotExists","avatarPath","getHomePath","avatarExists","isPathExists","USER_DEFAULT_AVATAR_FILE_PATH","pngMimeType","svgMimeType","avatarFile","avatarStream","Readable","generateAvatar","getInitials","listAppPasswords","Array","isArray","appPasswords","map","rest","generateAppPassword","userAppPasswordDto","slugName","createLightSlug","find","p","clearPassword","genPassword","appPassword","app","expiration","hashPassword","createdAt","unshift","deleteAppPassword","passwordName","filter","haveRole","USER","expMs","setOnlineStatus","onlineStatus","getOnlineUsers","userIds","usersWhitelist","browseGroups","group","groupFromName","parentGroup","members","browseGroupMembers","undefined","browseRootGroups","getGroup","groupId","withMembers","asAdmin","getGroupWithMembers","createPersonalGroup","userCreateOrUpdateGroupDto","JSON","stringify","checkGroupNameExists","log","clearWhiteListCaches","updatePersonalGroup","Object","keys","length","currentGroup","isAdmin","type","MEMBER_TYPE","PGROUP","updateGroup","message","addUsersToGroup","userWhiteList","GROUP","m","indexOf","updateGroupMembers","add","groupRole","USER_GROUP_ROLE","MEMBER","updateUserFromPersonalGroup","updateUserFromGroupDto","userToUpdate","MANAGER","adminUsersManager","updateUserFromGroup","removeUserFromGroup","userToRemove","remove","leavePersonalGroup","userWhoLeaves","deletePersonalGroup","canDeletePersonalGroup","listGuests","getGuest","guestId","guest","checkUser","createGuest","createGuestDto","managers","push","createUserOrGuest","GUEST","updateGuest","updateGuestDto","isGuestManager","deleteGuest","deleteUserOrGuest","deleteSpace","isGuest","searchMembers","searchMembersDto","searchUsersOrGroups","notificationsManager","sendEmailNotification","NOTIFICATION_APP","AUTH_LOCKED","event","NOTIFICATION_APP_EVENT","ACTION","DELETE","element","url","Logger"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/applications/users/services/users-manager.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { MultipartFile } from '@fastify/multipart'\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport bcrypt from 'bcryptjs'\nimport { WriteStream } from 'fs'\nimport { createWriteStream } from 'node:fs'\nimport path from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { AUTH_SCOPE } from '../../../authentication/constants/scope'\nimport { LoginResponseDto } from '../../../authentication/dto/login-response.dto'\nimport { FastifyAuthenticatedRequest } from '../../../authentication/interfaces/auth-request.interface'\nimport { JwtIdentityPayload } from '../../../authentication/interfaces/jwt-payload.interface'\nimport { ACTION } from '../../../common/constants'\nimport { comparePassword, hashPassword } from '../../../common/functions'\nimport { generateAvatar, pngMimeType, svgMimeType } from '../../../common/image'\nimport { createLightSlug, genPassword } from '../../../common/shared'\nimport { configuration, serverConfig } from '../../../configuration/config.environment'\nimport { isPathExists, moveFiles } from '../../files/utils/files'\nimport { NOTIFICATION_APP, NOTIFICATION_APP_EVENT } from '../../notifications/constants/notifications'\nimport { NotificationsManager } from '../../notifications/services/notifications-manager.service'\nimport { MEMBER_TYPE } from '../constants/member'\nimport { USER_GROUP_ROLE, USER_MAX_PASSWORD_ATTEMPTS, USER_ONLINE_STATUS, USER_ROLE } from '../constants/user'\nimport type { UserCreateOrUpdateGroupDto } from '../dto/create-or-update-group.dto'\nimport type { CreateUserDto, UpdateUserDto, UpdateUserFromGroupDto } from '../dto/create-or-update-user.dto'\nimport type { SearchMembersDto } from '../dto/search-members.dto'\nimport type {\n UserAppPasswordDto,\n UserLanguageDto,\n UserNotificationDto,\n UserStorageIndexingDto,\n UserUpdatePasswordDto\n} from '../dto/user-properties.dto'\nimport type { GroupBrowse } from '../interfaces/group-browse.interface'\nimport type { GroupMember, GroupWithMembers } from '../interfaces/group-member'\nimport type { GuestUser } from '../interfaces/guest-user.interface'\nimport type { Member } from '../interfaces/member.interface'\nimport type { UserAppPassword, UserSecrets } from '../interfaces/user-secrets.interface'\nimport type { UserOnline } from '../interfaces/websocket.interface'\nimport { UserModel } from '../models/user.model'\nimport type { Group } from '../schemas/group.interface'\nimport type { UserGroup } from '../schemas/user-group.interface'\nimport type { User } from '../schemas/user.interface'\nimport { USER_AVATAR_FILE_NAME, USER_AVATAR_MAX_UPLOAD_SIZE, USER_DEFAULT_AVATAR_FILE_PATH } from '../utils/avatar'\nimport { AdminUsersManager } from './admin-users-manager.service'\nimport { UsersQueries } from './users-queries.service'\n\n@Injectable()\nexport class UsersManager {\n private readonly logger = new Logger(UsersManager.name)\n\n constructor(\n public readonly usersQueries: UsersQueries,\n private readonly adminUsersManager: AdminUsersManager,\n private readonly notificationsManager: NotificationsManager\n ) {}\n\n async fromUserId(id: number): Promise<UserModel> {\n const user: User = await this.usersQueries.from(id)\n return user ? new UserModel(user, true) : null\n }\n\n async findUser(loginOrEmail: string, removePassword: false): Promise<UserModel>\n async findUser(loginOrEmail: string, removePassword?: true): Promise<Omit<UserModel, 'password'>>\n async findUser(loginOrEmail: string, removePassword: boolean = true): Promise<Omit<UserModel, 'password'>> {\n const user: User = await this.usersQueries.from(null, loginOrEmail)\n return user ? new UserModel(user, removePassword) : null\n }\n\n async logUser(user: UserModel, password: string, ip: string, scope?: AUTH_SCOPE): Promise<UserModel> {\n this.validateUserAccess(user, ip)\n let authSuccess: boolean = await comparePassword(password, user.password)\n if (!authSuccess && scope) {\n authSuccess = await this.validateAppPassword(user, password, ip, scope)\n }\n this.updateAccesses(user, ip, authSuccess).catch((e: Error) => this.logger.error(`${this.logUser.name} - ${e}`))\n if (authSuccess) {\n await user.makePaths()\n return user\n }\n this.logger.warn(`${this.logUser.name} - bad password for *${user.login}*`)\n return null\n }\n\n validateUserAccess(user: UserModel, ip: string) {\n if (user.role === USER_ROLE.LINK) {\n this.logger.error(`${this.validateUserAccess.name} - guest link account ${user} is not authorized to login`)\n throw new HttpException('Account is not allowed', HttpStatus.FORBIDDEN)\n }\n if (!user.isActive || user.passwordAttempts >= USER_MAX_PASSWORD_ATTEMPTS) {\n this.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error(`${this.validateUserAccess.name} - ${e}`))\n this.logger.error(`${this.validateUserAccess.name} - user account *${user.login}* is locked`)\n this.notifyAccountLocked(user, ip)\n throw new HttpException('Account locked', HttpStatus.FORBIDDEN)\n }\n }\n\n async me(authUser: UserModel): Promise<Omit<LoginResponseDto, 'token'>> {\n const user = await this.fromUserId(authUser.id)\n if (!user) {\n this.logger.warn(`User *${authUser.login} (${authUser.id}) not found`)\n throw new HttpException(`User not found`, HttpStatus.NOT_FOUND)\n }\n user.impersonated = !!authUser.impersonatedFromId\n user.clientId = authUser.clientId\n return { user: user, server: serverConfig }\n }\n\n async compareUserPassword(userId: number, password: string): Promise<boolean> {\n return this.usersQueries.compareUserPassword(userId, password)\n }\n\n async updateLanguage(user: UserModel, userLanguageDto: UserLanguageDto) {\n if (!userLanguageDto.language) userLanguageDto.language = null\n if (!(await this.usersQueries.updateUserOrGuest(user.id, userLanguageDto))) {\n throw new HttpException('Unable to update language', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updatePassword(user: UserModel, userPasswordDto: UserUpdatePasswordDto) {\n const r = await this.usersQueries.selectUserProperties(user.id, ['password'])\n if (!r) {\n throw new HttpException('Unable to check password', HttpStatus.NOT_FOUND)\n }\n if (!(await comparePassword(userPasswordDto.oldPassword, r.password))) {\n throw new HttpException('Password mismatch', HttpStatus.BAD_REQUEST)\n }\n const hash = await bcrypt.hash(userPasswordDto.newPassword, 10)\n if (!(await this.usersQueries.updateUserOrGuest(user.id, { password: hash }))) {\n throw new HttpException('Unable to update password', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateNotification(user: UserModel, userNotificationDto: UserNotificationDto) {\n if (!(await this.usersQueries.updateUserOrGuest(user.id, userNotificationDto))) {\n throw new HttpException('Unable to update notification preference', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateStorageIndexing(user: UserModel, userStorageIndexingDto: UserStorageIndexingDto) {\n if (!(await this.usersQueries.updateUserOrGuest(user.id, userStorageIndexingDto))) {\n throw new HttpException('Unable to update full-text search preference', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateAvatar(req: FastifyAuthenticatedRequest) {\n const part: MultipartFile = await req.file({ limits: { fileSize: USER_AVATAR_MAX_UPLOAD_SIZE } })\n if (!part.mimetype.startsWith('image/')) {\n throw new HttpException('Unsupported file type', HttpStatus.BAD_REQUEST)\n }\n const dstPath = path.join(req.user.tmpPath, USER_AVATAR_FILE_NAME)\n try {\n await pipeline(part.file, createWriteStream(dstPath))\n } catch (e) {\n this.logger.error(`${this.updateAvatar.name} - ${e}`)\n throw new HttpException('Unable to upload avatar', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n if (part.file.truncated) {\n this.logger.warn(`${this.updateAvatar.name} - image is too large`)\n throw new HttpException('Image is too large (5MB max)', HttpStatus.PAYLOAD_TOO_LARGE)\n }\n try {\n await moveFiles(dstPath, path.join(req.user.homePath, USER_AVATAR_FILE_NAME), true)\n } catch (e) {\n this.logger.error(`${this.updateAvatar.name} - ${e}`)\n throw new HttpException('Unable to create avatar', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateSecrets(userId: number, secrets: UserSecrets) {\n const userSecrets = await this.usersQueries.getUserSecrets(userId)\n const updatedSecrets = { ...userSecrets, ...secrets }\n if (!(await this.usersQueries.updateUserOrGuest(userId, { secrets: updatedSecrets }))) {\n throw new HttpException('Unable to update secrets', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updateAccesses(user: UserModel, ip: string, success: boolean, isAuthTwoFa = false) {\n let passwordAttempts: number\n if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {\n // Do not reset password attempts if the login still requires 2FA validation\n passwordAttempts = user.passwordAttempts\n } else {\n passwordAttempts = success ? 0 : Math.min(user.passwordAttempts + 1, USER_MAX_PASSWORD_ATTEMPTS)\n }\n await this.usersQueries.updateUserOrGuest(user.id, {\n lastAccess: user.currentAccess,\n currentAccess: new Date(),\n lastIp: user.currentIp,\n currentIp: ip,\n passwordAttempts: passwordAttempts,\n isActive: user.isActive && passwordAttempts < USER_MAX_PASSWORD_ATTEMPTS\n })\n }\n\n async getAvatar(userLogin: string, generate: true, generateIsNotExists?: boolean): Promise<undefined>\n async getAvatar(userLogin: string, generate?: false, generateIsNotExists?: boolean): Promise<[path: string, mime: string]>\n async getAvatar(userLogin: string, generate: boolean = false, generateIsNotExists?: boolean): Promise<[path: string, mime: string]> {\n const avatarPath = path.join(UserModel.getHomePath(userLogin), USER_AVATAR_FILE_NAME)\n const avatarExists = await isPathExists(avatarPath)\n if (!avatarExists && generateIsNotExists) {\n generate = true\n }\n if (!generate) {\n return [avatarExists ? avatarPath : USER_DEFAULT_AVATAR_FILE_PATH, avatarExists ? pngMimeType : svgMimeType]\n }\n if (!(await isPathExists(UserModel.getHomePath(userLogin)))) {\n throw new HttpException(`Home path for user *${userLogin}* does not exist`, HttpStatus.FORBIDDEN)\n }\n const user: Partial<UserModel> = await this.findUser(userLogin)\n if (!user) {\n throw new HttpException(`avatar not found`, HttpStatus.NOT_FOUND)\n }\n const avatarFile: WriteStream = createWriteStream(avatarPath)\n const avatarStream: NodeJS.ReadableStream = await generateAvatar(user.getInitials())\n try {\n await pipeline(avatarStream, avatarFile)\n } catch (e) {\n this.logger.error(`${this.updateAvatar.name} - ${e}`)\n throw new HttpException('Unable to create avatar', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n if (generateIsNotExists) {\n return [avatarPath, pngMimeType]\n }\n }\n\n async listAppPasswords(user: UserModel): Promise<Omit<UserAppPassword, 'password'>[]> {\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n if (Array.isArray(secrets.appPasswords)) {\n // remove passwords from response\n return secrets.appPasswords.map(({ password, ...rest }: UserAppPassword) => rest)\n }\n return []\n }\n\n async generateAppPassword(user: UserModel, userAppPasswordDto: UserAppPasswordDto): Promise<UserAppPassword> {\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n const slugName = createLightSlug(userAppPasswordDto.name)\n if (Array.isArray(secrets.appPasswords) && secrets.appPasswords.find((p: UserAppPassword) => p.name === slugName)) {\n throw new HttpException('Name already used', HttpStatus.BAD_REQUEST)\n }\n secrets.appPasswords = Array.isArray(secrets.appPasswords) ? secrets.appPasswords : []\n const clearPassword = genPassword(24)\n const appPassword: UserAppPassword = {\n name: createLightSlug(userAppPasswordDto.name),\n app: userAppPasswordDto.app,\n expiration: userAppPasswordDto.expiration,\n password: await hashPassword(clearPassword),\n createdAt: new Date(),\n currentIp: null,\n currentAccess: null,\n lastIp: null,\n lastAccess: null\n }\n secrets.appPasswords.unshift(appPassword)\n if (!(await this.usersQueries.updateUserOrGuest(user.id, { secrets: secrets }))) {\n throw new HttpException('Unable to update app passwords', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n // return clear password only once\n return { ...appPassword, password: clearPassword }\n }\n\n async deleteAppPassword(user: UserModel, passwordName: string): Promise<void> {\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n if (!Array.isArray(secrets.appPasswords) || !secrets.appPasswords.find((p: UserAppPassword) => p.name === passwordName)) {\n throw new HttpException('App password not found', HttpStatus.NOT_FOUND)\n }\n secrets.appPasswords = secrets.appPasswords.filter((p: UserAppPassword) => p.name !== passwordName)\n if (!(await this.usersQueries.updateUserOrGuest(user.id, { secrets: secrets }))) {\n throw new HttpException('Unable to delete app password', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async validateAppPassword(user: UserModel, password: string, ip: string, scope: AUTH_SCOPE): Promise<boolean> {\n if (!scope || !user.haveRole(USER_ROLE.USER)) return false\n const secrets = await this.usersQueries.getUserSecrets(user.id)\n if (!Array.isArray(secrets.appPasswords)) return false\n for (const p of secrets.appPasswords) {\n if (p.app !== scope) continue\n const expMs = p.expiration ? new Date(p.expiration) : null\n if (p.expiration && new Date() > expMs) continue // expired\n if (await comparePassword(password, p.password)) {\n p.lastAccess = p.currentAccess\n p.currentAccess = new Date()\n p.lastIp = p.currentIp\n p.currentIp = ip\n // update accesses\n this.usersQueries\n .updateUserOrGuest(user.id, { secrets: secrets })\n .catch((e: Error) => this.logger.error(`${this.validateAppPassword.name} - ${e}`))\n return true\n }\n }\n return false\n }\n\n setOnlineStatus(user: JwtIdentityPayload, onlineStatus: USER_ONLINE_STATUS) {\n this.usersQueries.setOnlineStatus(user.id, onlineStatus).catch((e: Error) => this.logger.error(`${this.setOnlineStatus.name} - ${e}`))\n }\n\n getOnlineUsers(userIds: number[]): Promise<UserOnline[]> {\n return this.usersQueries.getOnlineUsers(userIds)\n }\n\n async usersWhitelist(userId: number): Promise<number[]> {\n return this.usersQueries.usersWhitelist(userId)\n }\n\n async browseGroups(user: UserModel, name: string): Promise<GroupBrowse> {\n if (name) {\n const group: Pick<Group, 'id' | 'name' | 'type'> & { role: UserGroup['role'] } = await this.usersQueries.groupFromName(user.id, name)\n if (!group) {\n throw new HttpException('Group not found', HttpStatus.NOT_FOUND)\n }\n return { parentGroup: group, members: await this.usersQueries.browseGroupMembers(group.id) }\n }\n return { parentGroup: undefined, members: await this.usersQueries.browseRootGroups(user.id) }\n }\n\n async getGroup(user: UserModel, groupId: number, withMembers?: true, asAdmin?: boolean): Promise<GroupWithMembers>\n async getGroup(user: UserModel, groupId: number, withMembers: false, asAdmin?: boolean): Promise<GroupMember>\n async getGroup(user: UserModel, groupId: number, withMembers = true, asAdmin = false): Promise<GroupMember | GroupWithMembers> {\n const group = withMembers\n ? await this.usersQueries.getGroupWithMembers(user.id, groupId, asAdmin)\n : await this.usersQueries.getGroup(user.id, groupId, asAdmin)\n if (!group) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n return group\n }\n\n async createPersonalGroup(user: UserModel, userCreateOrUpdateGroupDto: UserCreateOrUpdateGroupDto): Promise<GroupMember> {\n if (!userCreateOrUpdateGroupDto.name) {\n this.logger.error(`${this.createPersonalGroup.name} - missing group name : ${JSON.stringify(userCreateOrUpdateGroupDto)}`)\n throw new HttpException('Group name is missing', HttpStatus.BAD_REQUEST)\n }\n if (await this.usersQueries.checkGroupNameExists(userCreateOrUpdateGroupDto.name)) {\n throw new HttpException('Name already used', HttpStatus.BAD_REQUEST)\n }\n try {\n const groupId: number = await this.usersQueries.createPersonalGroup(user.id, userCreateOrUpdateGroupDto)\n this.logger.log(`${this.createPersonalGroup.name} - group (${groupId}) was created : ${JSON.stringify(userCreateOrUpdateGroupDto)}`)\n // clear user whitelists\n this.usersQueries.clearWhiteListCaches([user.id])\n return this.getGroup(user, groupId, false)\n } catch (e) {\n this.logger.error(`${this.createPersonalGroup.name} - group was not created : ${JSON.stringify(userCreateOrUpdateGroupDto)} : ${e}`)\n throw new HttpException('Unable to create group', HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async updatePersonalGroup(user: UserModel, groupId: number, userCreateOrUpdateGroupDto: UserCreateOrUpdateGroupDto): Promise<GroupMember> {\n if (!Object.keys(userCreateOrUpdateGroupDto).length) {\n throw new HttpException('No changes to update', HttpStatus.BAD_REQUEST)\n }\n const currentGroup: GroupMember = await this.getGroup(user, groupId, false, user.isAdmin)\n if (currentGroup.type !== MEMBER_TYPE.PGROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n if (userCreateOrUpdateGroupDto.name && (await this.usersQueries.checkGroupNameExists(userCreateOrUpdateGroupDto.name))) {\n throw new HttpException('Name already used', HttpStatus.BAD_REQUEST)\n }\n try {\n await this.usersQueries.updateGroup(groupId, userCreateOrUpdateGroupDto)\n } catch (e) {\n throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR)\n }\n return this.getGroup(user, groupId, false, user.isAdmin)\n }\n\n async addUsersToGroup(user: UserModel, groupId: number, userIds: number[]): Promise<void> {\n const currentGroup: GroupWithMembers = await this.getGroup(user, groupId)\n // only users can be added to users groups\n // guests and users can be added to personal groups\n const userWhiteList: number[] = await this.usersQueries.usersWhitelist(\n user.id,\n currentGroup.type === MEMBER_TYPE.GROUP ? USER_ROLE.USER : undefined\n )\n // ignore user ids that are already group members & filter on user ids allowed to current user\n userIds = userIds.filter((id) => !currentGroup.members.find((m) => m.id === id)).filter((id) => userWhiteList.indexOf(id) > -1)\n if (!userIds.length) {\n throw new HttpException('No users to add to group', HttpStatus.BAD_REQUEST)\n }\n return this.usersQueries.updateGroupMembers(groupId, { add: userIds.map((id) => ({ id: id, groupRole: USER_GROUP_ROLE.MEMBER })) })\n }\n\n async updateUserFromPersonalGroup(user: UserModel, groupId: number, userId: number, updateUserFromGroupDto: UpdateUserFromGroupDto): Promise<void> {\n const currentGroup: GroupWithMembers = await this.getGroup(user, groupId)\n if (currentGroup.type !== MEMBER_TYPE.PGROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n const userToUpdate = currentGroup.members.find((m) => m.id === userId)\n if (!userToUpdate) {\n throw new HttpException('User was not found', HttpStatus.BAD_REQUEST)\n }\n if (userToUpdate.groupRole !== updateUserFromGroupDto.role) {\n if (userToUpdate.groupRole === USER_GROUP_ROLE.MANAGER) {\n if (currentGroup.members.filter((m) => m.groupRole === USER_GROUP_ROLE.MANAGER).length === 1) {\n throw new HttpException('Group must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n return this.adminUsersManager.updateUserFromGroup(groupId, userId, updateUserFromGroupDto)\n }\n }\n\n async removeUserFromGroup(user: UserModel, groupId: number, userId: number): Promise<void> {\n const currentGroup: GroupWithMembers = await this.getGroup(user, groupId)\n const userToRemove = currentGroup.members.find((m) => m.id === userId)\n if (!userToRemove) {\n throw new HttpException('User was not found', HttpStatus.BAD_REQUEST)\n }\n if (userToRemove.groupRole === USER_GROUP_ROLE.MANAGER) {\n if (currentGroup.type === MEMBER_TYPE.GROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n if (currentGroup.members.filter((m) => m.groupRole === USER_GROUP_ROLE.MANAGER).length === 1) {\n throw new HttpException('Group must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n return this.usersQueries.updateGroupMembers(groupId, { remove: [userId] })\n }\n\n async leavePersonalGroup(user: UserModel, groupId: number): Promise<void> {\n const currentGroup: GroupWithMembers = await this.usersQueries.getGroupWithMembers(user.id, groupId, true)\n if (!currentGroup || currentGroup.type === MEMBER_TYPE.GROUP) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n const userWhoLeaves = currentGroup.members.find((m) => m.id === user.id)\n if (!userWhoLeaves) {\n throw new HttpException('User was not found', HttpStatus.BAD_REQUEST)\n }\n if (userWhoLeaves.groupRole === USER_GROUP_ROLE.MANAGER) {\n if (currentGroup.members.filter((m) => m.groupRole === USER_GROUP_ROLE.MANAGER).length === 1) {\n throw new HttpException('Group must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n try {\n await this.usersQueries.updateGroupMembers(groupId, { remove: [user.id] })\n this.logger.log(`${this.leavePersonalGroup.name} - user (${user.id}) has left group (${groupId})`)\n } catch (e) {\n this.logger.error(`${this.leavePersonalGroup.name} - user (${user.id}) has not left group (${groupId}) : ${e}`)\n throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR)\n }\n }\n\n async deletePersonalGroup(user: UserModel, groupId: number): Promise<void> {\n if (!(await this.usersQueries.canDeletePersonalGroup(user.id, groupId))) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n if (await this.usersQueries.deletePersonalGroup(groupId)) {\n this.logger.log(`${this.deletePersonalGroup.name} - group (${groupId}) was deleted`)\n } else {\n this.logger.warn(`${this.deletePersonalGroup.name} - group (${groupId}) does not exist`)\n throw new HttpException('Unable to delete group', HttpStatus.BAD_REQUEST)\n }\n }\n\n listGuests(user: UserModel): Promise<GuestUser[]> {\n return this.usersQueries.listGuests(null, user.id)\n }\n\n async getGuest(user: UserModel, guestId: number): Promise<GuestUser> {\n const guest: GuestUser = await this.usersQueries.listGuests(guestId, user.id)\n this.adminUsersManager.checkUser(guest, true)\n return guest\n }\n\n async createGuest(user: UserModel, createGuestDto: CreateUserDto): Promise<GuestUser> {\n // filter managers that are allowed for current user\n const userWhiteList = await this.usersQueries.usersWhitelist(user.id, USER_ROLE.USER)\n createGuestDto.managers = createGuestDto.managers.filter((id) => userWhiteList.indexOf(id) > -1)\n if (createGuestDto.managers.indexOf(user.id) === -1) {\n // force user as manager during creation\n createGuestDto.managers.push(user.id)\n }\n // clear user whitelists\n this.usersQueries.clearWhiteListCaches([user.id])\n return this.adminUsersManager.createUserOrGuest(createGuestDto, USER_ROLE.GUEST, true)\n }\n\n async updateGuest(user: UserModel, guestId: number, updateGuestDto: UpdateUserDto): Promise<GuestUser> {\n if (!Object.keys(updateGuestDto).length) {\n throw new HttpException('No changes to update', HttpStatus.BAD_REQUEST)\n }\n if (updateGuestDto.managers) {\n // filter managers that are allowed for current user\n const userWhiteList = await this.usersQueries.usersWhitelist(user.id, USER_ROLE.USER)\n updateGuestDto.managers = updateGuestDto.managers.filter((id) => userWhiteList.indexOf(id) > -1)\n if (!updateGuestDto.managers.length) {\n throw new HttpException('Guest must have at least one manager', HttpStatus.BAD_REQUEST)\n }\n }\n if (!(await this.usersQueries.isGuestManager(user.id, guestId))) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n const guest = await this.adminUsersManager.updateUserOrGuest(guestId, updateGuestDto, USER_ROLE.GUEST)\n return guest.managers.find((m) => m.id === user.id) ? guest : null\n }\n\n async deleteGuest(user: UserModel, guestId: number): Promise<void> {\n const guest = await this.usersQueries.isGuestManager(user.id, guestId)\n if (!guest) {\n throw new HttpException('You are not allowed to do this action', HttpStatus.FORBIDDEN)\n }\n // guest has no space but a temporary directory\n return this.adminUsersManager.deleteUserOrGuest(guest.id, guest.login, { deleteSpace: true, isGuest: true })\n }\n\n searchMembers(user: UserModel, searchMembersDto: SearchMembersDto): Promise<Member[]> {\n return this.usersQueries.searchUsersOrGroups(searchMembersDto, user.id)\n }\n\n private notifyAccountLocked(user: UserModel, ip: string) {\n this.notificationsManager\n .sendEmailNotification([user], {\n app: NOTIFICATION_APP.AUTH_LOCKED,\n event: NOTIFICATION_APP_EVENT.AUTH_LOCKED[ACTION.DELETE],\n element: null,\n url: ip\n })\n .catch((e: Error) => this.logger.error(`${this.validateUserAccess.name} - ${e}`))\n }\n}\n"],"names":["UsersManager","fromUserId","id","user","usersQueries","from","UserModel","findUser","loginOrEmail","removePassword","logUser","password","ip","scope","validateUserAccess","authSuccess","comparePassword","validateAppPassword","updateAccesses","catch","e","logger","error","name","makePaths","warn","login","role","USER_ROLE","LINK","HttpException","HttpStatus","FORBIDDEN","isActive","passwordAttempts","USER_MAX_PASSWORD_ATTEMPTS","notifyAccountLocked","me","authUser","NOT_FOUND","impersonated","impersonatedFromId","clientId","server","serverConfig","compareUserPassword","userId","updateLanguage","userLanguageDto","language","updateUserOrGuest","INTERNAL_SERVER_ERROR","updatePassword","userPasswordDto","r","selectUserProperties","oldPassword","BAD_REQUEST","hash","bcrypt","newPassword","updateNotification","userNotificationDto","updateStorageIndexing","userStorageIndexingDto","updateAvatar","req","part","file","limits","fileSize","USER_AVATAR_MAX_UPLOAD_SIZE","mimetype","startsWith","dstPath","path","join","tmpPath","USER_AVATAR_FILE_NAME","pipeline","createWriteStream","truncated","PAYLOAD_TOO_LARGE","moveFiles","homePath","updateSecrets","secrets","userSecrets","getUserSecrets","updatedSecrets","success","isAuthTwoFa","configuration","auth","mfa","totp","enabled","twoFaEnabled","Math","min","lastAccess","currentAccess","Date","lastIp","currentIp","getAvatar","userLogin","generate","generateIsNotExists","avatarPath","getHomePath","avatarExists","isPathExists","USER_DEFAULT_AVATAR_FILE_PATH","pngMimeType","svgMimeType","avatarFile","avatarStream","generateAvatar","getInitials","listAppPasswords","Array","isArray","appPasswords","map","rest","generateAppPassword","userAppPasswordDto","slugName","createLightSlug","find","p","clearPassword","genPassword","appPassword","app","expiration","hashPassword","createdAt","unshift","deleteAppPassword","passwordName","filter","haveRole","USER","expMs","setOnlineStatus","onlineStatus","getOnlineUsers","userIds","usersWhitelist","browseGroups","group","groupFromName","parentGroup","members","browseGroupMembers","undefined","browseRootGroups","getGroup","groupId","withMembers","asAdmin","getGroupWithMembers","createPersonalGroup","userCreateOrUpdateGroupDto","JSON","stringify","checkGroupNameExists","log","clearWhiteListCaches","updatePersonalGroup","Object","keys","length","currentGroup","isAdmin","type","MEMBER_TYPE","PGROUP","updateGroup","message","addUsersToGroup","userWhiteList","GROUP","m","indexOf","updateGroupMembers","add","groupRole","USER_GROUP_ROLE","MEMBER","updateUserFromPersonalGroup","updateUserFromGroupDto","userToUpdate","MANAGER","adminUsersManager","updateUserFromGroup","removeUserFromGroup","userToRemove","remove","leavePersonalGroup","userWhoLeaves","deletePersonalGroup","canDeletePersonalGroup","listGuests","getGuest","guestId","guest","checkUser","createGuest","createGuestDto","managers","push","createUserOrGuest","GUEST","updateGuest","updateGuestDto","isGuestManager","deleteGuest","deleteUserOrGuest","deleteSpace","isGuest","searchMembers","searchMembersDto","searchUsersOrGroups","notificationsManager","sendEmailNotification","NOTIFICATION_APP","AUTH_LOCKED","event","NOTIFICATION_APP_EVENT","ACTION","DELETE","element","url","Logger"],"mappings":"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"}
|
|
@@ -42,13 +42,18 @@ let WebSocketUsers = class WebSocketUsers {
|
|
|
42
42
|
server.on(this.internalEventOnlineUsers, (cb)=>cb(this.filterUserRooms(this.server.sockets.adapter.rooms.keys())));
|
|
43
43
|
setTimeout(()=>this.initialized = true, this.waitTime);
|
|
44
44
|
}
|
|
45
|
+
beforeApplicationShutdown(_signal) {
|
|
46
|
+
this.shuttingDown = true;
|
|
47
|
+
}
|
|
45
48
|
async handleConnection(socket) {
|
|
49
|
+
if (this.shuttingDown) return;
|
|
46
50
|
socket.join(`${_websocket.USER_ROOM_PREFIX}${socket.user.id}`);
|
|
47
51
|
this.sendOnlineUser(socket.user, parseInt(socket.handshake.query.onlineStatus || '0')).catch((e)=>this.logger.error(`${this.handleConnection.name} - ${e}`));
|
|
48
52
|
this.sendAllOnlineUsers(socket.user.id).catch((e)=>this.logger.error(`${this.handleConnection.name} - ${e}`));
|
|
49
53
|
this.logger.log(`Connected: *${socket.user.login}* (${socket.user.id}) [${socket.id}] ${(0, _utils.getClientAddress)(socket)} ${socket.handshake.headers['user-agent']}`);
|
|
50
54
|
}
|
|
51
55
|
async handleDisconnect(socket) {
|
|
56
|
+
if (this.shuttingDown) return;
|
|
52
57
|
socket.leave(`${_websocket.USER_ROOM_PREFIX}${socket.user.id}`);
|
|
53
58
|
this.sendOfflineUser(socket.user.id).catch((e)=>this.logger.error(`${this.handleDisconnect.name} - ${e}`));
|
|
54
59
|
this.logger.log(`Disconnected: *${socket.user.login}* (${socket.user.id}) [${socket.id}] ${(0, _utils.getClientAddress)(socket)} ${socket.handshake.headers['user-agent']}`);
|
|
@@ -153,6 +158,7 @@ let WebSocketUsers = class WebSocketUsers {
|
|
|
153
158
|
this.internalEventOnlineUsers = 'listOnlineUsers';
|
|
154
159
|
this.logger = new _common.Logger(WebSocketUsers.name);
|
|
155
160
|
this.initialized = false;
|
|
161
|
+
this.shuttingDown = false;
|
|
156
162
|
this.waitTime = 3000; //ms
|
|
157
163
|
}
|
|
158
164
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/applications/users/users.gateway.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Logger } from '@nestjs/common'\nimport {\n MessageBody,\n OnGatewayConnection,\n OnGatewayDisconnect,\n OnGatewayInit,\n SubscribeMessage,\n WebSocketGateway,\n WebSocketServer\n} from '@nestjs/websockets'\nimport { Server } from 'socket.io'\nimport { JwtIdentityPayload } from '../../authentication/interfaces/jwt-payload.interface'\nimport { sleep } from '../../common/functions'\nimport { GetWsUser } from '../../infrastructure/websocket/decorators/web-socket-user.decorator'\nimport { AuthenticatedSocketIO } from '../../infrastructure/websocket/interfaces/auth-socket-io.interface'\nimport { getClientAddress } from '../../infrastructure/websocket/utils'\nimport { USER_ONLINE_STATUS } from './constants/user'\nimport { USER_ROOM_PREFIX, USERS_WS } from './constants/websocket'\nimport { EventChangeOnlineStatus, EventUpdateOnlineStatus, UserOnline } from './interfaces/websocket.interface'\nimport { UsersManager } from './services/users-manager.service'\n\n@WebSocketGateway()\nexport class WebSocketUsers implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {\n @WebSocketServer() server: Server\n private readonly internalEventOnlineUsers = 'listOnlineUsers'\n private readonly logger = new Logger(WebSocketUsers.name)\n private initialized = false\n private readonly waitTime: number = 3000 //ms\n // to show local rooms : this.server.of('/').adapter.rooms\n\n constructor(private readonly usersManager: UsersManager) {}\n\n afterInit(server: Server): any {\n server.on(this.internalEventOnlineUsers, (cb) => cb(this.filterUserRooms(this.server.sockets.adapter.rooms.keys())))\n setTimeout(() => (this.initialized = true), this.waitTime)\n }\n\n async handleConnection(socket: AuthenticatedSocketIO): Promise<void> {\n socket.join(`${USER_ROOM_PREFIX}${socket.user.id}`)\n this.sendOnlineUser(socket.user, parseInt((socket.handshake.query.onlineStatus as string) || '0')).catch((e: Error) =>\n this.logger.error(`${this.handleConnection.name} - ${e}`)\n )\n this.sendAllOnlineUsers(socket.user.id).catch((e: Error) => this.logger.error(`${this.handleConnection.name} - ${e}`))\n this.logger.log(\n `Connected: *${socket.user.login}* (${socket.user.id}) [${socket.id}] ${getClientAddress(socket)} ${socket.handshake.headers['user-agent']}`\n )\n }\n\n async handleDisconnect(socket: AuthenticatedSocketIO): Promise<void> {\n socket.leave(`${USER_ROOM_PREFIX}${socket.user.id}`)\n this.sendOfflineUser(socket.user.id).catch((e: Error) => this.logger.error(`${this.handleDisconnect.name} - ${e}`))\n this.logger.log(\n `Disconnected: *${socket.user.login}* (${socket.user.id}) [${socket.id}] ${getClientAddress(socket)} ${socket.handshake.headers['user-agent']}`\n )\n }\n\n @SubscribeMessage(USERS_WS.EVENTS.ONLINE_STATUS)\n setOnlineStatus(@GetWsUser() user: JwtIdentityPayload, @MessageBody() body: EventChangeOnlineStatus) {\n if (body.store) {\n // store in db\n this.usersManager.setOnlineStatus(user, body.status)\n }\n this.sendOnlineStatus(user.id, body.status).catch((e: Error) => this.logger.error(`${this.setOnlineStatus.name} - ${e}`))\n }\n\n private sendMessageToUsers(userIds: number[], eventName: string, body: any) {\n if (userIds.length) {\n this.server.to(userIds.map((uid) => `${USER_ROOM_PREFIX}${uid}`)).emit(eventName, body)\n }\n }\n\n private async sendOnlineUser(user: JwtIdentityPayload, status: USER_ONLINE_STATUS) {\n if (!this.initialized) {\n // all online users will be propagated in the next seconds\n return\n }\n if (status === USER_ONLINE_STATUS.OFFLINE) {\n // user's online status is offline, do not send update to others\n return\n }\n const userIdsToNotify: number[] = await this.getOnlineUserIdsWhitelist(user.id)\n if (userIdsToNotify.length) {\n const onlineUser: UserOnline = {\n id: user.id,\n login: user.login,\n email: user.email,\n fullName: user.fullName,\n onlineStatus: status\n } satisfies UserOnline\n this.sendMessageToUsers(userIdsToNotify, USERS_WS.EVENTS.ONLINE_USER, onlineUser)\n }\n }\n\n private async sendOfflineUser(userId: number): Promise<void> {\n // avoid sending offline event on quick reconnects\n await sleep(this.waitTime)\n // check if user room is empty before send event\n if ((await this.getOnlineUserIds()).indexOf(userId) === -1) {\n this.sendOnlineStatus(userId, USER_ONLINE_STATUS.OFFLINE).catch((e: Error) => this.logger.error(`${this.sendOfflineUser.name} - ${e}`))\n }\n }\n\n private async sendAllOnlineUsers(toUserId: number) {\n if (!this.initialized) {\n // wait for all reconnections before propagating all online users\n await sleep(this.waitTime)\n }\n const onlineUserIds: number[] = await this.getOnlineUserIdsWhitelist(toUserId)\n if (onlineUserIds.length) {\n const onlineUsers: UserOnline[] = await this.usersManager.getOnlineUsers(onlineUserIds)\n if (onlineUsers.length) {\n this.sendMessageToUsers([toUserId], USERS_WS.EVENTS.ONLINE_USERS, onlineUsers)\n }\n }\n }\n\n private async sendOnlineStatus(userId: number, status: USER_ONLINE_STATUS) {\n const userIdsToNotify: number[] = await this.getOnlineUserIdsWhitelist(userId, false)\n if (userIdsToNotify.length) {\n const onlineStatus: EventUpdateOnlineStatus = { userId: userId, status: status }\n this.sendMessageToUsers(userIdsToNotify, USERS_WS.EVENTS.ONLINE_STATUS, onlineStatus)\n }\n }\n\n private async getOnlineUserIdsWhitelist(userId: number, excludeUser = true): Promise<number[]> {\n /* get online users visible for the user */\n const currentOnlineUserIds: number[] = await this.getOnlineUserIds()\n if (currentOnlineUserIds.length) {\n let usersWhitelist: number[] = await this.usersManager.usersWhitelist(userId)\n if (usersWhitelist) {\n if (excludeUser) {\n usersWhitelist = usersWhitelist.filter((uid) => uid !== userId)\n }\n return usersWhitelist.filter((uid) => currentOnlineUserIds.indexOf(uid) > -1)\n }\n }\n return []\n }\n\n private async getOnlineUserIds(): Promise<number[]> {\n /* get all online users from all workers */\n const localUserIds: number[] = this.filterUserRooms(this.server.sockets.adapter.rooms.keys())\n const remoteUserIds: number[][] = await this.server.serverSideEmitWithAck(this.internalEventOnlineUsers)\n return [...new Set([...localUserIds, ...remoteUserIds].flat())]\n }\n\n private filterUserRooms(rooms: Iterable<string>): number[] {\n return [...rooms].filter((r) => r.startsWith(USER_ROOM_PREFIX)).map((ur) => parseInt(ur.split(USER_ROOM_PREFIX).pop()))\n }\n}\n"],"names":["WebSocketUsers","afterInit","server","on","internalEventOnlineUsers","cb","filterUserRooms","sockets","adapter","rooms","keys","setTimeout","initialized","waitTime","handleConnection","socket","join","USER_ROOM_PREFIX","user","id","sendOnlineUser","parseInt","handshake","query","onlineStatus","catch","e","logger","error","name","sendAllOnlineUsers","log","login","getClientAddress","headers","handleDisconnect","leave","sendOfflineUser","setOnlineStatus","body","store","usersManager","status","sendOnlineStatus","sendMessageToUsers","userIds","eventName","length","to","map","uid","emit","USER_ONLINE_STATUS","OFFLINE","userIdsToNotify","getOnlineUserIdsWhitelist","onlineUser","email","fullName","USERS_WS","EVENTS","ONLINE_USER","userId","sleep","getOnlineUserIds","indexOf","toUserId","onlineUserIds","onlineUsers","getOnlineUsers","ONLINE_USERS","ONLINE_STATUS","excludeUser","currentOnlineUserIds","usersWhitelist","filter","localUserIds","remoteUserIds","serverSideEmitWithAck","Set","flat","r","startsWith","ur","split","pop","Logger"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/applications/users/users.gateway.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { BeforeApplicationShutdown, Logger } from '@nestjs/common'\nimport {\n MessageBody,\n OnGatewayConnection,\n OnGatewayDisconnect,\n OnGatewayInit,\n SubscribeMessage,\n WebSocketGateway,\n WebSocketServer\n} from '@nestjs/websockets'\nimport { Server } from 'socket.io'\nimport { JwtIdentityPayload } from '../../authentication/interfaces/jwt-payload.interface'\nimport { sleep } from '../../common/functions'\nimport { GetWsUser } from '../../infrastructure/websocket/decorators/web-socket-user.decorator'\nimport { AuthenticatedSocketIO } from '../../infrastructure/websocket/interfaces/auth-socket-io.interface'\nimport { getClientAddress } from '../../infrastructure/websocket/utils'\nimport { USER_ONLINE_STATUS } from './constants/user'\nimport { USER_ROOM_PREFIX, USERS_WS } from './constants/websocket'\nimport { EventChangeOnlineStatus, EventUpdateOnlineStatus, UserOnline } from './interfaces/websocket.interface'\nimport { UsersManager } from './services/users-manager.service'\n\n@WebSocketGateway()\nexport class WebSocketUsers implements BeforeApplicationShutdown, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {\n @WebSocketServer() server: Server\n private readonly internalEventOnlineUsers = 'listOnlineUsers'\n private readonly logger = new Logger(WebSocketUsers.name)\n private initialized = false\n private shuttingDown = false\n private readonly waitTime: number = 3000 //ms\n // to show local rooms : this.server.of('/').adapter.rooms\n\n constructor(private readonly usersManager: UsersManager) {}\n\n afterInit(server: Server): any {\n server.on(this.internalEventOnlineUsers, (cb) => cb(this.filterUserRooms(this.server.sockets.adapter.rooms.keys())))\n setTimeout(() => (this.initialized = true), this.waitTime)\n }\n\n beforeApplicationShutdown(_signal?: string) {\n this.shuttingDown = true\n }\n\n async handleConnection(socket: AuthenticatedSocketIO): Promise<void> {\n if (this.shuttingDown) return\n socket.join(`${USER_ROOM_PREFIX}${socket.user.id}`)\n this.sendOnlineUser(socket.user, parseInt((socket.handshake.query.onlineStatus as string) || '0')).catch((e: Error) =>\n this.logger.error(`${this.handleConnection.name} - ${e}`)\n )\n this.sendAllOnlineUsers(socket.user.id).catch((e: Error) => this.logger.error(`${this.handleConnection.name} - ${e}`))\n this.logger.log(\n `Connected: *${socket.user.login}* (${socket.user.id}) [${socket.id}] ${getClientAddress(socket)} ${socket.handshake.headers['user-agent']}`\n )\n }\n\n async handleDisconnect(socket: AuthenticatedSocketIO): Promise<void> {\n if (this.shuttingDown) return\n socket.leave(`${USER_ROOM_PREFIX}${socket.user.id}`)\n this.sendOfflineUser(socket.user.id).catch((e: Error) => this.logger.error(`${this.handleDisconnect.name} - ${e}`))\n this.logger.log(\n `Disconnected: *${socket.user.login}* (${socket.user.id}) [${socket.id}] ${getClientAddress(socket)} ${socket.handshake.headers['user-agent']}`\n )\n }\n\n @SubscribeMessage(USERS_WS.EVENTS.ONLINE_STATUS)\n setOnlineStatus(@GetWsUser() user: JwtIdentityPayload, @MessageBody() body: EventChangeOnlineStatus) {\n if (body.store) {\n // store in db\n this.usersManager.setOnlineStatus(user, body.status)\n }\n this.sendOnlineStatus(user.id, body.status).catch((e: Error) => this.logger.error(`${this.setOnlineStatus.name} - ${e}`))\n }\n\n private sendMessageToUsers(userIds: number[], eventName: string, body: any) {\n if (userIds.length) {\n this.server.to(userIds.map((uid) => `${USER_ROOM_PREFIX}${uid}`)).emit(eventName, body)\n }\n }\n\n private async sendOnlineUser(user: JwtIdentityPayload, status: USER_ONLINE_STATUS) {\n if (!this.initialized) {\n // all online users will be propagated in the next seconds\n return\n }\n if (status === USER_ONLINE_STATUS.OFFLINE) {\n // user's online status is offline, do not send update to others\n return\n }\n const userIdsToNotify: number[] = await this.getOnlineUserIdsWhitelist(user.id)\n if (userIdsToNotify.length) {\n const onlineUser: UserOnline = {\n id: user.id,\n login: user.login,\n email: user.email,\n fullName: user.fullName,\n onlineStatus: status\n } satisfies UserOnline\n this.sendMessageToUsers(userIdsToNotify, USERS_WS.EVENTS.ONLINE_USER, onlineUser)\n }\n }\n\n private async sendOfflineUser(userId: number): Promise<void> {\n // avoid sending offline event on quick reconnects\n await sleep(this.waitTime)\n // check if user room is empty before send event\n if ((await this.getOnlineUserIds()).indexOf(userId) === -1) {\n this.sendOnlineStatus(userId, USER_ONLINE_STATUS.OFFLINE).catch((e: Error) => this.logger.error(`${this.sendOfflineUser.name} - ${e}`))\n }\n }\n\n private async sendAllOnlineUsers(toUserId: number) {\n if (!this.initialized) {\n // wait for all reconnections before propagating all online users\n await sleep(this.waitTime)\n }\n const onlineUserIds: number[] = await this.getOnlineUserIdsWhitelist(toUserId)\n if (onlineUserIds.length) {\n const onlineUsers: UserOnline[] = await this.usersManager.getOnlineUsers(onlineUserIds)\n if (onlineUsers.length) {\n this.sendMessageToUsers([toUserId], USERS_WS.EVENTS.ONLINE_USERS, onlineUsers)\n }\n }\n }\n\n private async sendOnlineStatus(userId: number, status: USER_ONLINE_STATUS) {\n const userIdsToNotify: number[] = await this.getOnlineUserIdsWhitelist(userId, false)\n if (userIdsToNotify.length) {\n const onlineStatus: EventUpdateOnlineStatus = { userId: userId, status: status }\n this.sendMessageToUsers(userIdsToNotify, USERS_WS.EVENTS.ONLINE_STATUS, onlineStatus)\n }\n }\n\n private async getOnlineUserIdsWhitelist(userId: number, excludeUser = true): Promise<number[]> {\n /* get online users visible for the user */\n const currentOnlineUserIds: number[] = await this.getOnlineUserIds()\n if (currentOnlineUserIds.length) {\n let usersWhitelist: number[] = await this.usersManager.usersWhitelist(userId)\n if (usersWhitelist) {\n if (excludeUser) {\n usersWhitelist = usersWhitelist.filter((uid) => uid !== userId)\n }\n return usersWhitelist.filter((uid) => currentOnlineUserIds.indexOf(uid) > -1)\n }\n }\n return []\n }\n\n private async getOnlineUserIds(): Promise<number[]> {\n /* get all online users from all workers */\n const localUserIds: number[] = this.filterUserRooms(this.server.sockets.adapter.rooms.keys())\n const remoteUserIds: number[][] = await this.server.serverSideEmitWithAck(this.internalEventOnlineUsers)\n return [...new Set([...localUserIds, ...remoteUserIds].flat())]\n }\n\n private filterUserRooms(rooms: Iterable<string>): number[] {\n return [...rooms].filter((r) => r.startsWith(USER_ROOM_PREFIX)).map((ur) => parseInt(ur.split(USER_ROOM_PREFIX).pop()))\n }\n}\n"],"names":["WebSocketUsers","afterInit","server","on","internalEventOnlineUsers","cb","filterUserRooms","sockets","adapter","rooms","keys","setTimeout","initialized","waitTime","beforeApplicationShutdown","_signal","shuttingDown","handleConnection","socket","join","USER_ROOM_PREFIX","user","id","sendOnlineUser","parseInt","handshake","query","onlineStatus","catch","e","logger","error","name","sendAllOnlineUsers","log","login","getClientAddress","headers","handleDisconnect","leave","sendOfflineUser","setOnlineStatus","body","store","usersManager","status","sendOnlineStatus","sendMessageToUsers","userIds","eventName","length","to","map","uid","emit","USER_ONLINE_STATUS","OFFLINE","userIdsToNotify","getOnlineUserIdsWhitelist","onlineUser","email","fullName","USERS_WS","EVENTS","ONLINE_USER","userId","sleep","getOnlineUserIds","indexOf","toUserId","onlineUserIds","onlineUsers","getOnlineUsers","ONLINE_USERS","ONLINE_STATUS","excludeUser","currentOnlineUserIds","usersWhitelist","filter","localUserIds","remoteUserIds","serverSideEmitWithAck","Set","flat","r","startsWith","ur","split","pop","Logger"],"mappings":"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"}
|
package/server/common/image.js
CHANGED
|
@@ -27,80 +27,67 @@ _export(exports, {
|
|
|
27
27
|
},
|
|
28
28
|
get svgMimeType () {
|
|
29
29
|
return svgMimeType;
|
|
30
|
+
},
|
|
31
|
+
get webpMimeType () {
|
|
32
|
+
return webpMimeType;
|
|
30
33
|
}
|
|
31
34
|
});
|
|
32
|
-
const _sharp = /*#__PURE__*/ _interop_require_default(require("sharp"));
|
|
33
|
-
const _resvgjs = require("@resvg/resvg-js");
|
|
34
35
|
const _promises = /*#__PURE__*/ _interop_require_default(require("node:fs/promises"));
|
|
36
|
+
const _nodeos = /*#__PURE__*/ _interop_require_default(require("node:os"));
|
|
35
37
|
const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
|
|
38
|
+
const _nodeutil = require("node:util");
|
|
39
|
+
const _sharp = /*#__PURE__*/ _interop_require_default(require("sharp"));
|
|
40
|
+
const _texttosvg = /*#__PURE__*/ _interop_require_default(require("text-to-svg" // Sharp settings
|
|
41
|
+
));
|
|
36
42
|
function _interop_require_default(obj) {
|
|
37
43
|
return obj && obj.__esModule ? obj : {
|
|
38
44
|
default: obj
|
|
39
45
|
};
|
|
40
46
|
}
|
|
47
|
+
// Sharp settings
|
|
48
|
+
_sharp.default.cache(false);
|
|
49
|
+
_sharp.default.concurrency(Math.min(2, _nodeos.default.cpus()?.length || 1));
|
|
41
50
|
const pngMimeType = 'image/png';
|
|
42
51
|
const svgMimeType = 'image/svg+xml';
|
|
52
|
+
const webpMimeType = 'image/webp';
|
|
53
|
+
const avatarSize = 256;
|
|
54
|
+
const fontPath = _nodepath.default.join(__dirname, 'fonts', 'avatar.ttf');
|
|
55
|
+
const loadTextToSVG = (0, _nodeutil.promisify)(_texttosvg.default.load.bind(_texttosvg.default));
|
|
56
|
+
let textToSvgCache = null;
|
|
43
57
|
async function generateThumbnail(filePath, size) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
return image.resize(width, height, {
|
|
60
|
-
fit: 'inside'
|
|
61
|
-
}).png({
|
|
62
|
-
compressionLevel: 0
|
|
63
|
-
}).toBuffer();
|
|
58
|
+
return (0, _sharp.default)(filePath, {
|
|
59
|
+
sequentialRead: true,
|
|
60
|
+
limitInputPixels: 268e6 // protects against extremely large images
|
|
61
|
+
}).rotate().resize({
|
|
62
|
+
width: size,
|
|
63
|
+
height: size,
|
|
64
|
+
fit: 'inside',
|
|
65
|
+
kernel: 'nearest',
|
|
66
|
+
withoutEnlargement: true,
|
|
67
|
+
fastShrinkOnLoad: true // true by default, added for clarity
|
|
68
|
+
}).webp({
|
|
69
|
+
quality: 80,
|
|
70
|
+
effort: 0,
|
|
71
|
+
alphaQuality: 90
|
|
72
|
+
});
|
|
64
73
|
}
|
|
65
74
|
async function generateAvatar(initials) {
|
|
66
|
-
const
|
|
67
|
-
const height = 256;
|
|
75
|
+
const tts = await getTextToSvg();
|
|
68
76
|
const { backgroundColor, foregroundColor } = randomColor();
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
font-family: 'Avatar';
|
|
76
|
-
src: url('data:font/ttf;base64,${fontBase64}') format('truetype');
|
|
77
|
-
}
|
|
78
|
-
text {
|
|
79
|
-
font-family: 'Avatar', sans-serif;
|
|
80
|
-
font-size: 150px;
|
|
81
|
-
fill: ${foregroundColor};
|
|
82
|
-
dominant-baseline: central;
|
|
83
|
-
text-anchor: middle;
|
|
84
|
-
}
|
|
85
|
-
</style>
|
|
86
|
-
<rect width="100%" height="100%" fill="${backgroundColor}" />
|
|
87
|
-
<text x="50%" y="50%">${initials}</text>
|
|
88
|
-
</svg>
|
|
89
|
-
`;
|
|
90
|
-
// Rasterize SVG to PNG
|
|
91
|
-
const resvg = new _resvgjs.Resvg(svg, {
|
|
92
|
-
fitTo: {
|
|
93
|
-
mode: 'width',
|
|
94
|
-
value: width
|
|
95
|
-
},
|
|
96
|
-
font: {
|
|
97
|
-
fontFiles: [
|
|
98
|
-
fontPath
|
|
99
|
-
],
|
|
100
|
-
loadSystemFonts: false
|
|
101
|
-
}
|
|
77
|
+
const fontSize = fitFontSize(tts, initials, avatarSize * 0.8, 170);
|
|
78
|
+
const d = tts.getD(initials, {
|
|
79
|
+
x: avatarSize / 2,
|
|
80
|
+
y: avatarSize / 2.1,
|
|
81
|
+
fontSize,
|
|
82
|
+
anchor: 'center middle'
|
|
102
83
|
});
|
|
103
|
-
|
|
84
|
+
const svg = `
|
|
85
|
+
<svg width="${avatarSize}" height="${avatarSize}" viewBox="0 0 ${avatarSize} ${avatarSize}"
|
|
86
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
87
|
+
<rect width="100%" height="100%" fill="${backgroundColor}"/>
|
|
88
|
+
<path d="${d}" fill="${foregroundColor}" />
|
|
89
|
+
</svg>`.trim();
|
|
90
|
+
return (0, _sharp.default)(Buffer.from(svg, 'utf8')).png();
|
|
104
91
|
}
|
|
105
92
|
async function convertImageToBase64(imgPath) {
|
|
106
93
|
const base64String = await _promises.default.readFile(imgPath, {
|
|
@@ -125,5 +112,22 @@ function randomColor() {
|
|
|
125
112
|
foregroundColor: brightness > 180 ? '#000000' : '#ffffff'
|
|
126
113
|
};
|
|
127
114
|
}
|
|
115
|
+
function fitFontSize(tts, text, box, start = 170) {
|
|
116
|
+
// Heuristic to make the text occupy ~80% of the available width
|
|
117
|
+
let size = start;
|
|
118
|
+
// Lower bound to prevent infinite loops when the font renders very small
|
|
119
|
+
while(size > 20){
|
|
120
|
+
const m = tts.getMetrics(text, {
|
|
121
|
+
fontSize: size,
|
|
122
|
+
anchor: 'center middle'
|
|
123
|
+
});
|
|
124
|
+
if (m.width <= box) break;
|
|
125
|
+
size -= 4;
|
|
126
|
+
}
|
|
127
|
+
return size;
|
|
128
|
+
}
|
|
129
|
+
function getTextToSvg() {
|
|
130
|
+
return textToSvgCache ??= loadTextToSVG(fontPath);
|
|
131
|
+
}
|
|
128
132
|
|
|
129
133
|
//# sourceMappingURL=image.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../backend/src/common/image.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport
|
|
1
|
+
{"version":3,"sources":["../../../backend/src/common/image.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { promisify } from 'node:util'\nimport sharp from 'sharp'\nimport TextToSVG from 'text-to-svg' // Sharp settings\n\n// Sharp settings\nsharp.cache(false)\nsharp.concurrency(Math.min(2, os.cpus()?.length || 1))\n\n// Constants\nexport const pngMimeType = 'image/png'\nexport const svgMimeType = 'image/svg+xml'\nexport const webpMimeType = 'image/webp'\nconst avatarSize = 256\nconst fontPath = path.join(__dirname, 'fonts', 'avatar.ttf')\nconst loadTextToSVG = promisify(TextToSVG.load.bind(TextToSVG))\nlet textToSvgCache: Promise<TextToSVG> | null = null\n\nexport async function generateThumbnail(filePath: string, size: number): Promise<Readable> {\n return sharp(filePath, {\n sequentialRead: true, // sequential read = more efficient I/O\n limitInputPixels: 268e6 // protects against extremely large images\n })\n .rotate()\n .resize({\n width: size,\n height: size,\n fit: 'inside',\n kernel: 'nearest',\n withoutEnlargement: true,\n fastShrinkOnLoad: true // true by default, added for clarity\n })\n .webp({ quality: 80, effort: 0, alphaQuality: 90 })\n}\n\nexport async function generateAvatar(initials: string): Promise<NodeJS.ReadableStream> {\n const tts = await getTextToSvg()\n const { backgroundColor, foregroundColor } = randomColor()\n const fontSize = fitFontSize(tts, initials, avatarSize * 0.8, 170)\n\n const d = tts.getD(initials, {\n x: avatarSize / 2,\n y: avatarSize / 2.1,\n fontSize,\n anchor: 'center middle'\n })\n\n const svg = `\n<svg width=\"${avatarSize}\" height=\"${avatarSize}\" viewBox=\"0 0 ${avatarSize} ${avatarSize}\"\n xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"100%\" height=\"100%\" fill=\"${backgroundColor}\"/>\n <path d=\"${d}\" fill=\"${foregroundColor}\" />\n</svg>`.trim()\n\n return sharp(Buffer.from(svg, 'utf8')).png()\n}\n\nexport async function convertImageToBase64(imgPath: string) {\n const base64String = await fs.readFile(imgPath, { encoding: 'base64' })\n return `data:image/png;base64,${base64String}`\n}\n\nfunction randomColor() {\n let color = ''\n while (color.length < 6) {\n /* sometimes the returned value does not have\n * the 6 digits needed, so we do it again until\n * it does\n */\n color = Math.floor(Math.random() * 16777215).toString(16)\n }\n const red = parseInt(color.substring(0, 2), 16)\n const green = parseInt(color.substring(2, 4), 16)\n const blue = parseInt(color.substring(4, 6), 16)\n const brightness = red * 0.299 + green * 0.587 + blue * 0.114\n\n return {\n backgroundColor: `#${color}`,\n foregroundColor: brightness > 180 ? '#000000' : '#ffffff'\n }\n}\n\nfunction fitFontSize(tts: TextToSVG, text: string, box: number, start = 170): number {\n // Heuristic to make the text occupy ~80% of the available width\n let size = start\n // Lower bound to prevent infinite loops when the font renders very small\n while (size > 20) {\n const m = tts.getMetrics(text, { fontSize: size, anchor: 'center middle' })\n if (m.width <= box) break\n size -= 4\n }\n return size\n}\n\nfunction getTextToSvg(): Promise<TextToSVG> {\n return (textToSvgCache ??= loadTextToSVG(fontPath) as Promise<TextToSVG>)\n}\n"],"names":["convertImageToBase64","generateAvatar","generateThumbnail","pngMimeType","svgMimeType","webpMimeType","sharp","cache","concurrency","Math","min","os","cpus","length","avatarSize","fontPath","path","join","__dirname","loadTextToSVG","promisify","TextToSVG","load","bind","textToSvgCache","filePath","size","sequentialRead","limitInputPixels","rotate","resize","width","height","fit","kernel","withoutEnlargement","fastShrinkOnLoad","webp","quality","effort","alphaQuality","initials","tts","getTextToSvg","backgroundColor","foregroundColor","randomColor","fontSize","fitFontSize","d","getD","x","y","anchor","svg","trim","Buffer","from","png","imgPath","base64String","fs","readFile","encoding","color","floor","random","toString","red","parseInt","substring","green","blue","brightness","text","box","start","m","getMetrics"],"mappings":"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"}
|
|
@@ -471,32 +471,6 @@ trademarks does not indicate endorsement of the trademark holder by Font
|
|
|
471
471
|
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
|
472
472
|
to represent the company, product, or service to which they refer.**
|
|
473
473
|
|
|
474
|
-
--------------------------------------------------------------------------------
|
|
475
|
-
Package: mobx
|
|
476
|
-
License: "MIT"
|
|
477
|
-
|
|
478
|
-
The MIT License (MIT)
|
|
479
|
-
|
|
480
|
-
Copyright (c) 2015 Michel Weststrate
|
|
481
|
-
|
|
482
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
483
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
484
|
-
in the Software without restriction, including without limitation the rights
|
|
485
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
486
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
487
|
-
furnished to do so, subject to the following conditions:
|
|
488
|
-
|
|
489
|
-
The above copyright notice and this permission notice shall be included in all
|
|
490
|
-
copies or substantial portions of the Software.
|
|
491
|
-
|
|
492
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
493
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
494
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
495
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
496
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
497
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
498
|
-
SOFTWARE.
|
|
499
|
-
|
|
500
474
|
--------------------------------------------------------------------------------
|
|
501
475
|
Package: @ali-hm/angular-tree-component
|
|
502
476
|
License: "MIT"
|