@lucidcms/core
Version:
The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.
1 lines • 13.7 kB
Source Map (JSON)
{"version":3,"file":"update-single.mjs","names":["formatter","constants","userServices.updateMultipleRoles","userServices.updateMultipleTenants","securityAuditServices.logSecurityAudit","emailServices.sendEmail"],"sources":["../../../src/services/users/update-single.ts"],"sourcesContent":["import { scrypt } from \"@noble/hashes/scrypt.js\";\nimport constants from \"../../constants/constants.js\";\nimport formatter from \"../../libs/formatters/index.js\";\nimport { copy } from \"../../libs/i18n/index.js\";\nimport {\n\tEmailChangeRequestsRepository,\n\tUsersRepository,\n} from \"../../libs/repositories/index.js\";\nimport generateSecret from \"../../utils/helpers/generate-secret.js\";\nimport {\n\tformatEmailSubject,\n\tmultiTenancyEnabled,\n} from \"../../utils/helpers/index.js\";\nimport { normalizeEmailInput } from \"../../utils/helpers/normalize-input.js\";\nimport type { ServiceFn } from \"../../utils/services/types.js\";\nimport { invalidateAuthCache } from \"../auth/helpers/auth-cache.js\";\nimport {\n\temailServices,\n\tsecurityAuditServices,\n\tuserServices,\n} from \"../index.js\";\nimport prepareUpdateSingleAuditLogs from \"./helpers/prepare-update-single-audit-logs.js\";\nimport validateUserTenantMemberships from \"./helpers/validate-user-tenant-memberships.js\";\n\nconst updateSingle: ServiceFn<\n\t[\n\t\t{\n\t\t\tuserId: number;\n\t\t\tfirstName?: string;\n\t\t\tlastName?: string;\n\t\t\tusername?: string;\n\t\t\temail?: string;\n\t\t\tpassword?: string;\n\t\t\troleIds?: number[];\n\t\t\tsuperAdmin?: boolean;\n\t\t\ttriggerPasswordReset?: boolean;\n\t\t\tisDeleted?: boolean;\n\t\t\tisLocked?: boolean;\n\t\t\ttenantKeys?: string[];\n\t\t\tauth: {\n\t\t\t\tid: number;\n\t\t\t\tsuperAdmin: boolean;\n\t\t\t};\n\t\t},\n\t],\n\tnumber\n> = async (context, data) => {\n\tconst Users = new UsersRepository(context.db.client, context.config.db);\n\tconst EmailChangeRequests = new EmailChangeRequestsRepository(\n\t\tcontext.db.client,\n\t\tcontext.config.db,\n\t);\n\tconst normalizedEmail =\n\t\tdata.email !== undefined ? normalizeEmailInput(data.email) : undefined;\n\n\tif (data.auth.id === data.userId) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage: copy(\"server:core.users.self.update.denied\"),\n\t\t\t\tstatus: 400,\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst userRes = await Users.selectSinglePreset({\n\t\ttenantKey: context.request.tenantKey,\n\t\twhere: [\n\t\t\t{\n\t\t\t\tkey: \"id\",\n\t\t\t\toperator: \"=\",\n\t\t\t\tvalue: data.userId,\n\t\t\t},\n\t\t\t{\n\t\t\t\tkey: \"is_deleted\",\n\t\t\t\toperator: \"=\",\n\t\t\t\tvalue: context.config.db.getDefault(\"boolean\", \"false\"),\n\t\t\t},\n\t\t],\n\t\tvalidation: {\n\t\t\tenabled: true,\n\t\t\tdefaultError: {\n\t\t\t\tmessage: copy(\"server:core.user.not.found.message\"),\n\t\t\t\tstatus: 404,\n\t\t\t},\n\t\t},\n\t});\n\tif (userRes.error) return userRes;\n\n\tconst currentSuperAdmin = formatter.formatBoolean(\n\t\tuserRes.data.super_admin ?? false,\n\t);\n\tconst existingTenantKeys = multiTenancyEnabled(context.config)\n\t\t? userRes.data.tenants.map((tenant) => tenant.tenant_key)\n\t\t: [];\n\tconst targetSuperAdmin =\n\t\tdata.auth.superAdmin && data.superAdmin !== undefined\n\t\t\t? data.superAdmin\n\t\t\t: currentSuperAdmin;\n\n\tconst targetTenantKeys =\n\t\tdata.auth.superAdmin && data.tenantKeys !== undefined\n\t\t\t? Array.from(new Set(data.tenantKeys))\n\t\t\t: existingTenantKeys;\n\n\tconst tenantMembershipsError = validateUserTenantMemberships({\n\t\tconfig: context.config,\n\t\ttenantKeys: targetTenantKeys,\n\t\ttargetSuperAdmin,\n\t});\n\tif (tenantMembershipsError !== undefined) {\n\t\treturn {\n\t\t\terror: tenantMembershipsError,\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst [emailExists, reservedEmail, usernameExists] = await Promise.all([\n\t\tnormalizedEmail !== undefined && normalizedEmail !== userRes.data.email\n\t\t\t? Users.selectSingle({\n\t\t\t\t\tselect: [\"email\"],\n\t\t\t\t\twhere: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkey: \"email\",\n\t\t\t\t\t\t\toperator: \"=\",\n\t\t\t\t\t\t\tvalue: normalizedEmail,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkey: \"id\",\n\t\t\t\t\t\t\toperator: \"!=\",\n\t\t\t\t\t\t\tvalue: data.userId,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t: undefined,\n\t\tnormalizedEmail !== undefined && normalizedEmail !== userRes.data.email\n\t\t\t? EmailChangeRequests.selectReservedByEmail({\n\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t})\n\t\t\t: undefined,\n\t\tdata.username\n\t\t\t? Users.selectSingle({\n\t\t\t\t\tselect: [\"username\"],\n\t\t\t\t\twhere: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkey: \"username\",\n\t\t\t\t\t\t\toperator: \"=\",\n\t\t\t\t\t\t\tvalue: data.username,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t: undefined,\n\t]);\n\tif (emailExists?.error) return emailExists;\n\tif (reservedEmail?.error) return reservedEmail;\n\tif (usernameExists?.error) return usernameExists;\n\n\tif (\n\t\tnormalizedEmail !== undefined &&\n\t\tnormalizedEmail !== userRes.data.email &&\n\t\t(emailExists?.data !== undefined || reservedEmail?.data !== undefined)\n\t) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 400,\n\t\t\t\terrors: {\n\t\t\t\t\temail: {\n\t\t\t\t\t\tcode: \"invalid\",\n\t\t\t\t\t\tmessage: copy(\"server:core.users.email.duplicate\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\tif (data.username !== undefined && usernameExists?.data !== undefined) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 400,\n\t\t\t\terrors: {\n\t\t\t\t\tusername: {\n\t\t\t\t\t\tcode: \"invalid\",\n\t\t\t\t\t\tmessage: copy(\"server:core.users.username.duplicate\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tlet hashedPassword: string | undefined;\n\tlet encryptSecret: string | undefined;\n\tif (data.password) {\n\t\tconst genSecret = generateSecret(context.config.secrets.encryption);\n\t\t// Hash password using scrypt\n\t\thashedPassword = Buffer.from(\n\t\t\tscrypt(data.password, genSecret.secret, constants.scrypt),\n\t\t).toString(\"base64\");\n\t\tencryptSecret = genSecret.encryptSecret;\n\t}\n\n\tconst auditLogsRes = await prepareUpdateSingleAuditLogs(context, {\n\t\tuserId: data.userId,\n\t\tperformedBy: data.auth.id,\n\t\tcurrentUser: {\n\t\t\temail: userRes.data.email,\n\t\t\tsuperAdmin: userRes.data.super_admin,\n\t\t\troles:\n\t\t\t\tuserRes.data.roles?.map((role) => ({\n\t\t\t\t\tid: role.id,\n\t\t\t\t\tname:\n\t\t\t\t\t\trole.translations?.find(\n\t\t\t\t\t\t\t(translation) =>\n\t\t\t\t\t\t\t\ttranslation.locale_code ===\n\t\t\t\t\t\t\t\tcontext.config.localization.defaultLocale,\n\t\t\t\t\t\t)?.name ??\n\t\t\t\t\t\trole.translations?.find((translation) => translation.name !== null)\n\t\t\t\t\t\t\t?.name ??\n\t\t\t\t\t\t\"\",\n\t\t\t\t})) ?? [],\n\t\t},\n\t\tnormalizedEmail,\n\t\tpasswordChanged: hashedPassword !== undefined,\n\t\troleIds: data.roleIds,\n\t\tsuperAdmin: data.superAdmin,\n\t\tcanUpdateSuperAdmin: data.auth.superAdmin,\n\t});\n\tif (auditLogsRes.error) return auditLogsRes;\n\n\tconst [updateUserRes, updateRolesRes, updateTenantsRes] = await Promise.all([\n\t\tUsers.updateSingle({\n\t\t\tdata: {\n\t\t\t\tfirst_name: data.firstName,\n\t\t\t\tlast_name: data.lastName,\n\t\t\t\tusername: data.username,\n\t\t\t\temail: normalizedEmail,\n\t\t\t\tpassword: hashedPassword,\n\t\t\t\tsecret: encryptSecret,\n\t\t\t\tsuper_admin: data.auth.superAdmin ? data.superAdmin : undefined,\n\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\ttriggered_password_reset: data.triggerPasswordReset,\n\t\t\t\tis_deleted: data.isDeleted,\n\t\t\t\tis_locked: data.isLocked,\n\t\t\t},\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tkey: \"id\",\n\t\t\t\t\toperator: \"=\",\n\t\t\t\t\tvalue: data.userId,\n\t\t\t\t},\n\t\t\t],\n\t\t\treturning: [\"id\", \"first_name\", \"last_name\", \"email\"],\n\t\t\tvalidation: {\n\t\t\t\tenabled: true,\n\t\t\t\tdefaultError: {\n\t\t\t\t\tstatus: 500,\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tuserServices.updateMultipleRoles(context, {\n\t\t\tuserId: data.userId,\n\t\t\troleIds: data.roleIds,\n\t\t\ttenantKey: context.request.tenantKey,\n\t\t}),\n\t\tuserServices.updateMultipleTenants(context, {\n\t\t\tuserId: data.userId,\n\t\t\t//* only super admins can manage tenant memberships\n\t\t\ttenantKeys:\n\t\t\t\tdata.auth.superAdmin && data.tenantKeys !== undefined\n\t\t\t\t\t? targetTenantKeys\n\t\t\t\t\t: undefined,\n\t\t}),\n\t]);\n\tawait invalidateAuthCache(context);\n\n\tif (updateRolesRes.error) return updateRolesRes;\n\tif (updateUserRes.error) return updateUserRes;\n\tif (updateTenantsRes.error) return updateTenantsRes;\n\n\tconst auditResults = await Promise.all(\n\t\tauditLogsRes.data.logs.map((auditLog) =>\n\t\t\tsecurityAuditServices.logSecurityAudit(context, auditLog),\n\t\t),\n\t);\n\tfor (const auditRes of auditResults) {\n\t\tif (auditRes.error) return auditRes;\n\t}\n\n\tif (auditLogsRes.data.emailChange) {\n\t\tconst emailTenantKeys =\n\t\t\tdata.auth.superAdmin && data.tenantKeys !== undefined\n\t\t\t\t? targetTenantKeys\n\t\t\t\t: existingTenantKeys;\n\n\t\tconst sendEmailRes = await emailServices.sendEmail(context, {\n\t\t\ttemplate: constants.email.templates.emailChanged.key,\n\t\t\ttype: \"internal\",\n\t\t\tto: auditLogsRes.data.emailChange.newValue,\n\t\t\tsubject: (emailData) =>\n\t\t\t\tformatEmailSubject(\n\t\t\t\t\tcontext.translate(\"server:core.email.update.success.subject\"),\n\t\t\t\t\temailData.context.brand.name,\n\t\t\t\t),\n\t\t\tdata: {\n\t\t\t\tfirstName: data.firstName || userRes.data.first_name,\n\t\t\t},\n\t\t\ttenantKeys: emailTenantKeys,\n\t\t});\n\t\tif (sendEmailRes.error) return sendEmailRes;\n\t}\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: userRes.data.id,\n\t};\n};\n\nexport default updateSingle;\n"],"mappings":"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"}