JobContext.ts 6.3 KB

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