JobContext.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { forEachIn } from "@common/utils/forEachIn";
  2. import { SessionSchema } from "@/modules/DataModule/models/sessions/schema";
  3. import Job, { JobOptions } from "@/Job";
  4. import { Log } from "@/LogBook";
  5. import DataModule from "@/modules/DataModule";
  6. import { UserModel } from "@/modules/DataModule/models/users/schema";
  7. import { JobDerived } from "./types/JobDerived";
  8. import assertJobDerived from "./utils/assertJobDerived";
  9. import {
  10. GetMultipleModelPermissionsResult,
  11. GetSingleModelPermissionsResult
  12. } from "./modules/DataModule/models/users/jobs/GetModelPermissions";
  13. import { GetPermissionsResult } from "./modules/DataModule/models/users/jobs/GetPermissions";
  14. const permissionRegex =
  15. /^(?<moduleName>[a-z]+)\.(?<modelOrJobName>[A-z]+)\.(?<jobName>[A-z]+)(?:\.(?<modelId>[A-z0-9]{24}))?(?:\.(?<extra>[A-z]+))?$/;
  16. export default class JobContext {
  17. public readonly job: Job;
  18. private _session?: SessionSchema;
  19. private readonly _socketId?: string;
  20. private readonly _callbackRef?: string;
  21. public constructor(
  22. job: Job,
  23. options?: {
  24. session?: SessionSchema;
  25. socketId?: string;
  26. callbackRef?: string;
  27. }
  28. ) {
  29. this.job = job;
  30. this._session = options?.session;
  31. this._socketId = options?.socketId;
  32. this._callbackRef = options?.callbackRef;
  33. }
  34. /**
  35. * Log a message in the context of the current job, which automatically sets the category and data
  36. *
  37. * @param log - Log message or object
  38. */
  39. public log(log: string | Omit<Log, "timestamp" | "category">) {
  40. return this.job.log(log);
  41. }
  42. public getSession() {
  43. return this._session;
  44. }
  45. public setSession(session?: SessionSchema) {
  46. this._session = session;
  47. }
  48. public getSocketId() {
  49. return this._socketId;
  50. }
  51. public getCallbackRef() {
  52. return this._callbackRef;
  53. }
  54. public executeJob(
  55. // eslint-disable-next-line @typescript-eslint/ban-types
  56. JobClass: Function,
  57. payload?: unknown,
  58. options?: JobOptions
  59. ) {
  60. assertJobDerived(JobClass);
  61. return new (JobClass as JobDerived)(payload, {
  62. session: this._session,
  63. socketId: this._socketId,
  64. ...(options ?? {})
  65. }).execute();
  66. }
  67. public async getUser() {
  68. if (!this._session?.userId)
  69. throw new Error("No user found for session");
  70. const User = await DataModule.getModel<UserModel>("users");
  71. const user = await User.findById(this._session.userId);
  72. if (!user) throw new Error("No user found for session");
  73. return user;
  74. }
  75. public async assertLoggedIn() {
  76. if (!this._session?.userId)
  77. throw new Error("No user found for session");
  78. }
  79. public async assertPermission(permission: string) {
  80. let hasPermission = false;
  81. const { moduleName, modelOrJobName, jobName, modelId, extra } =
  82. permissionRegex.exec(permission)?.groups ?? {};
  83. if (moduleName === "data" && modelOrJobName && jobName) {
  84. const GetModelPermissions = DataModule.getJob(
  85. "users.getModelPermissions"
  86. );
  87. const permissions = (await this.executeJob(GetModelPermissions, {
  88. modelName: modelOrJobName,
  89. modelId
  90. })) as GetSingleModelPermissionsResult;
  91. let modelPermission = `data.${modelOrJobName}.${jobName}`;
  92. if (extra) modelPermission += `.${extra}`;
  93. hasPermission = permissions[modelPermission];
  94. } else {
  95. const GetPermissions = DataModule.getJob("users.getPermissions");
  96. const permissions = (await this.executeJob(
  97. GetPermissions
  98. )) as GetPermissionsResult;
  99. hasPermission = permissions[permission];
  100. }
  101. if (!hasPermission)
  102. throw new Error(
  103. `Insufficient permissions for permission ${permission}`
  104. );
  105. }
  106. public async assertPermissions(permissions: string[]) {
  107. const hasPermission: { [permission: string]: boolean } = {};
  108. permissions.forEach(permission => {
  109. hasPermission[permission] = false;
  110. });
  111. const permissionData = permissions.map(permission => {
  112. const { moduleName, modelOrJobName, jobName, modelId, extra } =
  113. permissionRegex.exec(permission)?.groups ?? {};
  114. return {
  115. permission,
  116. moduleName,
  117. modelOrJobName,
  118. jobName,
  119. modelId,
  120. extra
  121. };
  122. });
  123. const dataPermissions = permissionData.filter(
  124. ({ moduleName, modelOrJobName, jobName }) =>
  125. moduleName === "data" && modelOrJobName && jobName
  126. );
  127. const otherPermissions = permissionData.filter(
  128. ({ moduleName, modelOrJobName, jobName }) =>
  129. !(moduleName === "data" && modelOrJobName && jobName)
  130. );
  131. if (otherPermissions.length > 0) {
  132. const GetPermissions = DataModule.getJob("users.getPermissions");
  133. const permissions = (await this.executeJob(
  134. GetPermissions
  135. )) as GetPermissionsResult;
  136. otherPermissions.forEach(({ permission }) => {
  137. hasPermission[permission] = permissions[permission];
  138. });
  139. }
  140. if (dataPermissions.length > 0) {
  141. const dataPermissionsPerModel: any = {};
  142. dataPermissions.forEach(dataPermission => {
  143. const { modelOrJobName } = dataPermission;
  144. if (!Array.isArray(dataPermissionsPerModel[modelOrJobName]))
  145. dataPermissionsPerModel[modelOrJobName] = [];
  146. dataPermissionsPerModel[modelOrJobName].push(dataPermission);
  147. });
  148. const modelNames = Object.keys(dataPermissionsPerModel);
  149. const GetModelPermissions = DataModule.getJob(
  150. "users.getModelPermissions"
  151. );
  152. await forEachIn(modelNames, async modelName => {
  153. const dataPermissionsForThisModel =
  154. dataPermissionsPerModel[modelName];
  155. const modelIds = dataPermissionsForThisModel.map(
  156. ({ modelId }: { modelId: string }) => modelId
  157. );
  158. const permissions = (await this.executeJob(
  159. GetModelPermissions,
  160. {
  161. modelName,
  162. modelIds
  163. }
  164. )) as GetMultipleModelPermissionsResult;
  165. dataPermissionsForThisModel.forEach(
  166. ({
  167. modelOrJobName,
  168. jobName,
  169. modelId,
  170. extra,
  171. permission
  172. }: {
  173. modelOrJobName: string;
  174. jobName: string;
  175. modelId: string;
  176. extra?: string;
  177. permission: string;
  178. }) => {
  179. let modelPermission = `data.${modelOrJobName}.${jobName}`;
  180. if (extra) modelPermission += `.${extra}`;
  181. const permissionsForModelId = permissions[
  182. modelId
  183. ] as Record<string, boolean>;
  184. hasPermission[permission] =
  185. permissionsForModelId[modelPermission];
  186. }
  187. );
  188. });
  189. }
  190. if (
  191. Object.values(hasPermission).some(hasPermission => !hasPermission)
  192. ) {
  193. const missingPermissions = Object.entries(hasPermission)
  194. .filter(([, hasPermission]) => !hasPermission)
  195. .map(([permission]) => permission);
  196. throw new Error(
  197. `Insufficient permissions for permission(s) ${missingPermissions.join(
  198. ", "
  199. )}`
  200. );
  201. }
  202. }
  203. }