@oneuptime/common 7.0.3129 → 7.0.3140

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.
Files changed (89) hide show
  1. package/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +1 -1
  2. package/Models/AnalyticsModels/Index.ts +28 -1
  3. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +4 -0
  4. package/Models/DatabaseModels/Incident.ts +5 -0
  5. package/Models/DatabaseModels/Index.ts +35 -1
  6. package/Server/Middleware/ProjectAuthorization.ts +5 -3
  7. package/Server/Middleware/UserAuthorization.ts +32 -5
  8. package/Server/Services/AccessTokenService.ts +10 -154
  9. package/Server/Services/AnalyticsDatabaseService.ts +2 -2
  10. package/Server/Services/DatabaseService.ts +77 -4
  11. package/Server/Services/StatusPageService.ts +1 -1
  12. package/Server/Types/Permission/PermissionNamespace.ts +6 -0
  13. package/Server/Utils/APIKey/AccessPermission.ts +111 -0
  14. package/Server/Utils/Cookie.ts +15 -1
  15. package/Server/Utils/Monitor/MonitorResource.ts +57 -23
  16. package/Server/Utils/Realtime.ts +288 -79
  17. package/Server/Utils/UserPermission/UserPermission.ts +81 -0
  18. package/Tests/Server/Middleware/ProjectAuthorization.test.ts +3 -3
  19. package/Tests/Server/Middleware/UserAuthorization.test.ts +14 -10
  20. package/Tests/Server/Services/ScheduledMaintenanceService.test.ts +4 -2
  21. package/Tests/Server/Utils/Cookie.test.ts +1 -1
  22. package/Types/Database/TableMetadata.ts +5 -0
  23. package/Types/Realtime/EnableRealtimeEventsOn.ts +5 -0
  24. package/Types/Realtime/EventName.ts +5 -0
  25. package/Types/Realtime/ListenToModelEventJSON.ts +10 -0
  26. package/Types/Realtime/ModelEventType.ts +7 -0
  27. package/UI/Components/HeaderAlert/HeaderModelAlert.tsx +1 -1
  28. package/UI/Components/ModelList/ModelList.tsx +1 -1
  29. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  30. package/UI/Utils/Realtime.ts +28 -19
  31. package/Utils/Realtime.ts +10 -29
  32. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
  33. package/build/dist/Models/AnalyticsModels/Index.js +14 -0
  34. package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
  35. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  36. package/build/dist/Models/DatabaseModels/Incident.js +5 -0
  37. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  38. package/build/dist/Models/DatabaseModels/Index.js +16 -1
  39. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  40. package/build/dist/Server/Middleware/ProjectAuthorization.js +3 -3
  41. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  42. package/build/dist/Server/Middleware/UserAuthorization.js +13 -5
  43. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  44. package/build/dist/Server/Services/AccessTokenService.js +6 -108
  45. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  46. package/build/dist/Server/Services/AnalyticsDatabaseService.js +2 -2
  47. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  48. package/build/dist/Server/Services/DatabaseService.js +43 -4
  49. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  50. package/build/dist/Server/Services/StatusPageService.js +1 -1
  51. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  52. package/build/dist/Server/Types/Permission/PermissionNamespace.js +7 -0
  53. package/build/dist/Server/Types/Permission/PermissionNamespace.js.map +1 -0
  54. package/build/dist/Server/Utils/APIKey/AccessPermission.js +78 -0
  55. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -0
  56. package/build/dist/Server/Utils/Cookie.js +11 -1
  57. package/build/dist/Server/Utils/Cookie.js.map +1 -1
  58. package/build/dist/Server/Utils/Monitor/MonitorResource.js +58 -32
  59. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  60. package/build/dist/Server/Utils/Realtime.js +181 -51
  61. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  62. package/build/dist/Server/Utils/UserPermission/UserPermission.js +47 -0
  63. package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -0
  64. package/build/dist/Tests/Server/Middleware/ProjectAuthorization.test.js +3 -3
  65. package/build/dist/Tests/Server/Middleware/ProjectAuthorization.test.js.map +1 -1
  66. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +9 -8
  67. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  68. package/build/dist/Tests/Server/Services/ScheduledMaintenanceService.test.js +2 -2
  69. package/build/dist/Tests/Server/Services/ScheduledMaintenanceService.test.js.map +1 -1
  70. package/build/dist/Tests/Server/Utils/Cookie.test.js +1 -1
  71. package/build/dist/Tests/Server/Utils/Cookie.test.js.map +1 -1
  72. package/build/dist/Types/Database/TableMetadata.js +3 -0
  73. package/build/dist/Types/Database/TableMetadata.js.map +1 -1
  74. package/build/dist/Types/Realtime/EnableRealtimeEventsOn.js +2 -0
  75. package/build/dist/Types/Realtime/EnableRealtimeEventsOn.js.map +1 -0
  76. package/build/dist/Types/Realtime/EventName.js +6 -0
  77. package/build/dist/Types/Realtime/EventName.js.map +1 -0
  78. package/build/dist/Types/Realtime/ListenToModelEventJSON.js +2 -0
  79. package/build/dist/Types/Realtime/ListenToModelEventJSON.js.map +1 -0
  80. package/build/dist/Types/Realtime/ModelEventType.js +8 -0
  81. package/build/dist/Types/Realtime/ModelEventType.js.map +1 -0
  82. package/build/dist/UI/Utils/Realtime.js +17 -11
  83. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  84. package/build/dist/Utils/Realtime.js +6 -12
  85. package/build/dist/Utils/Realtime.js.map +1 -1
  86. package/package.json +2 -2
  87. package/Models/Permissions/Permission.ts +0 -0
  88. package/build/dist/Models/Permissions/Permission.js +0 -2
  89. package/build/dist/Models/Permissions/Permission.js.map +0 -1
@@ -0,0 +1,111 @@
1
+ import APIKeyPermission from "../../../Models/DatabaseModels/ApiKeyPermission";
2
+ import Label from "../../../Models/DatabaseModels/Label";
3
+ import LIMIT_MAX from "../../../Types/Database/LimitMax";
4
+ import ObjectID from "../../../Types/ObjectID";
5
+ import Permission, {
6
+ UserGlobalAccessPermission,
7
+ UserPermission,
8
+ UserTenantAccessPermission,
9
+ } from "../../../Types/Permission";
10
+ import ApiKeyPermissionService from "../../Services/ApiKeyPermissionService";
11
+ import UserPermissionUtil from "../UserPermission/UserPermission";
12
+
13
+ export default class APIKeyAccessPermission {
14
+ public static async getDefaultApiGlobalPermission(
15
+ projectId: ObjectID,
16
+ ): Promise<UserGlobalAccessPermission> {
17
+ return {
18
+ projectIds: [projectId],
19
+ globalPermissions: [
20
+ Permission.Public,
21
+ Permission.User,
22
+ Permission.CurrentUser,
23
+ ],
24
+ _type: "UserGlobalAccessPermission",
25
+ };
26
+ }
27
+
28
+ public static async getMasterKeyApiGlobalPermission(
29
+ projectId: ObjectID,
30
+ ): Promise<UserGlobalAccessPermission> {
31
+ return {
32
+ projectIds: [projectId],
33
+ globalPermissions: [
34
+ Permission.Public,
35
+ Permission.User,
36
+ Permission.CurrentUser,
37
+ Permission.ProjectOwner,
38
+ ],
39
+ _type: "UserGlobalAccessPermission",
40
+ };
41
+ }
42
+
43
+ public static async getMasterApiTenantAccessPermission(
44
+ projectId: ObjectID,
45
+ ): Promise<UserTenantAccessPermission> {
46
+ const userPermissions: Array<UserPermission> = [];
47
+
48
+ userPermissions.push({
49
+ permission: Permission.ProjectOwner,
50
+ labelIds: [],
51
+ _type: "UserPermission",
52
+ });
53
+
54
+ const permission: UserTenantAccessPermission =
55
+ UserPermissionUtil.getDefaultUserTenantAccessPermission(projectId);
56
+
57
+ permission.permissions = permission.permissions.concat(userPermissions);
58
+
59
+ return permission;
60
+ }
61
+
62
+ public static async getApiTenantAccessPermission(
63
+ projectId: ObjectID,
64
+ apiKeyId: ObjectID,
65
+ ): Promise<UserTenantAccessPermission> {
66
+ // get team permissions.
67
+ const apiKeyPermission: Array<APIKeyPermission> =
68
+ await ApiKeyPermissionService.findBy({
69
+ query: {
70
+ apiKeyId: apiKeyId,
71
+ },
72
+ select: {
73
+ permission: true,
74
+ labels: {
75
+ _id: true,
76
+ },
77
+ isBlockPermission: true,
78
+ },
79
+
80
+ limit: LIMIT_MAX,
81
+ skip: 0,
82
+ props: {
83
+ isRoot: true,
84
+ },
85
+ });
86
+
87
+ const userPermissions: Array<UserPermission> = [];
88
+
89
+ for (const apiPermission of apiKeyPermission) {
90
+ if (!apiPermission.labels) {
91
+ apiPermission.labels = [];
92
+ }
93
+
94
+ userPermissions.push({
95
+ permission: apiPermission.permission!,
96
+ labelIds: apiPermission.labels.map((label: Label) => {
97
+ return label.id!;
98
+ }),
99
+ isBlockPermission: apiPermission.isBlockPermission,
100
+ _type: "UserPermission",
101
+ });
102
+ }
103
+
104
+ const permission: UserTenantAccessPermission =
105
+ UserPermissionUtil.getDefaultUserTenantAccessPermission(projectId);
106
+
107
+ permission.permissions = permission.permissions.concat(userPermissions);
108
+
109
+ return permission;
110
+ }
111
+ }
@@ -11,6 +11,20 @@ import CookieName from "Common/Types/CookieName";
11
11
  export default class CookieUtil {
12
12
  // set cookie with express response
13
13
 
14
+ public static getCookiesFromCookieString(
15
+ cookieString: string,
16
+ ): Dictionary<string> {
17
+ const cookies: Dictionary<string> = {};
18
+ cookieString.split(";").forEach((cookie: string) => {
19
+ const parts: string[] = cookie.split("=");
20
+ const key: string = (parts[0] as string).trim() as string;
21
+ const value: string = parts[1] as string;
22
+ cookies[key] = value;
23
+ });
24
+
25
+ return cookies;
26
+ }
27
+
14
28
  public static setSSOCookie(data: {
15
29
  user: User;
16
30
  projectId: ObjectID;
@@ -141,7 +155,7 @@ export default class CookieUtil {
141
155
 
142
156
  // get cookie with express request
143
157
 
144
- public static getCookie(
158
+ public static getCookieFromExpressRequest(
145
159
  req: ExpressRequest,
146
160
  name: string,
147
161
  ): string | undefined {
@@ -237,11 +237,16 @@ export default class MonitorResourceUtil {
237
237
  `${dataToProcess.monitorId.toString()} - Saving monitor metrics`,
238
238
  );
239
239
 
240
- await this.saveMonitorMetrics({
241
- monitorId: monitor.id!,
242
- projectId: monitor.projectId!,
243
- dataToProcess: dataToProcess,
244
- });
240
+ try {
241
+ await this.saveMonitorMetrics({
242
+ monitorId: monitor.id!,
243
+ projectId: monitor.projectId!,
244
+ dataToProcess: dataToProcess,
245
+ });
246
+ } catch (err) {
247
+ logger.error("Unable to save metrics");
248
+ logger.error(err);
249
+ }
245
250
 
246
251
  logger.debug(
247
252
  `${dataToProcess.monitorId.toString()} - Monitor metrics saved`,
@@ -751,42 +756,71 @@ export default class MonitorResourceUtil {
751
756
  }): Promise<void> {
752
757
  // criteria filters are met, now process the actions.
753
758
 
759
+ const lastMonitorStatusTimeline: MonitorStatusTimeline | null =
760
+ await MonitorStatusTimelineService.findOneBy({
761
+ query: {
762
+ monitorId: input.monitor.id!,
763
+ projectId: input.monitor.projectId!,
764
+ },
765
+ select: {
766
+ _id: true,
767
+ monitorStatusId: true,
768
+ },
769
+ sort: {
770
+ startsAt: SortOrder.Descending,
771
+ },
772
+ props: {
773
+ isRoot: true,
774
+ },
775
+ });
776
+
777
+ let shouldUpdateStatus: boolean = false;
778
+
779
+ if (!lastMonitorStatusTimeline) {
780
+ // if monitor does not have any status timeline, then create one.
781
+ shouldUpdateStatus = true;
782
+ }
783
+
784
+ if (
785
+ input.criteriaInstance.data?.changeMonitorStatus &&
786
+ input.criteriaInstance.data?.monitorStatusId &&
787
+ input.criteriaInstance.data?.monitorStatusId.toString() !==
788
+ lastMonitorStatusTimeline?.id?.toString()
789
+ ) {
790
+ // if monitor status is changed, then create a new status timeline.
791
+ shouldUpdateStatus = true;
792
+ }
793
+
794
+ // check if the current status is same as the last status.
795
+
754
796
  if (
755
797
  input.criteriaInstance.data?.changeMonitorStatus &&
756
798
  input.criteriaInstance.data?.monitorStatusId &&
757
799
  input.criteriaInstance.data?.monitorStatusId.toString() !==
758
800
  input.monitor.currentMonitorStatusId?.toString()
759
801
  ) {
802
+ // if monitor status is changed, then create a new status timeline.
803
+ shouldUpdateStatus = true;
804
+ }
805
+
806
+ if (shouldUpdateStatus) {
760
807
  logger.debug(
761
- `${input.monitor.id?.toString()} - Change monitor status to ${input.criteriaInstance.data?.monitorStatusId.toString()}`,
808
+ `${input.monitor.id?.toString()} - Change monitor status to ${input.criteriaInstance.data?.monitorStatusId?.toString()}`,
762
809
  );
763
810
  // change monitor status
764
811
 
765
812
  const monitorStatusId: ObjectID | undefined =
766
813
  input.criteriaInstance.data?.monitorStatusId;
767
814
 
815
+ if (!monitorStatusId) {
816
+ throw new BadDataException("Monitor status is not defined.");
817
+ }
818
+
768
819
  //change monitor status.
769
820
 
770
821
  // get last status of this monitor.
771
822
 
772
823
  // get last monitor status timeline.
773
- const lastMonitorStatusTimeline: MonitorStatusTimeline | null =
774
- await MonitorStatusTimelineService.findOneBy({
775
- query: {
776
- monitorId: input.monitor.id!,
777
- projectId: input.monitor.projectId!,
778
- },
779
- select: {
780
- _id: true,
781
- monitorStatusId: true,
782
- },
783
- sort: {
784
- startsAt: SortOrder.Descending,
785
- },
786
- props: {
787
- isRoot: true,
788
- },
789
- });
790
824
 
791
825
  if (
792
826
  lastMonitorStatusTimeline &&
@@ -1,78 +1,78 @@
1
1
  import IO, { Socket, SocketServer } from "../Infrastructure/SocketIO";
2
2
  import logger from "./Logger";
3
- import AnalyticsBaseModel, {
4
- AnalyticsBaseModelType,
5
- } from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
6
- import BaseModel, {
7
- DatabaseBaseModelType,
8
- } from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
3
+ import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
4
+ import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
9
5
  import DatabaseType from "Common/Types/BaseDatabase/DatabaseType";
10
6
  import BadDataException from "Common/Types/Exception/BadDataException";
11
7
  import { JSONObject } from "Common/Types/JSON";
12
- import JSONFunctions from "Common/Types/JSONFunctions";
13
8
  import ObjectID from "Common/Types/ObjectID";
14
- import RealtimeUtil, {
15
- EventName,
16
- ListenToModelEventJSON,
17
- ModelEventType,
18
- } from "Common/Utils/Realtime";
9
+ import RealtimeUtil from "Common/Utils/Realtime";
10
+ import JSONWebTokenData from "../../Types/JsonWebTokenData";
11
+ import JSONWebToken from "./JsonWebToken";
12
+ import Permission, {
13
+ UserGlobalAccessPermission,
14
+ UserTenantAccessPermission,
15
+ } from "../../Types/Permission";
16
+ import { getModelTypeByName } from "../../Models/DatabaseModels/Index";
17
+ import { getModelTypeByName as getAnalyticsModelTypeByname } from "../../Models/AnalyticsModels/Index";
18
+ import DatabaseRequestType from "../Types/BaseDatabase/DatabaseRequestType";
19
+ import ModelPermission from "../../Types/BaseDatabase/ModelPermission";
20
+ import ModelEventType from "../../Types/Realtime/ModelEventType";
21
+ import ListenToModelEventJSON from "../../Types/Realtime/ListenToModelEventJSON";
22
+ import EventName from "../../Types/Realtime/EventName";
23
+ import CookieUtil from "./Cookie";
24
+ import Dictionary from "../../Types/Dictionary";
25
+ import UserPermissionUtil from "./UserPermission/UserPermission";
19
26
 
20
27
  export default abstract class Realtime {
21
28
  private static socketServer: SocketServer | null = null;
22
29
 
23
30
  public static isInitialized(): boolean {
24
- return this.socketServer !== null;
31
+ logger.debug("Checking if socket server is initialized");
32
+ const isInitialized: boolean = this.socketServer !== null;
33
+ logger.debug(`Socket server is initialized: ${isInitialized}`);
34
+ return isInitialized;
25
35
  }
26
36
 
27
37
  public static async init(): Promise<SocketServer | null> {
28
38
  if (!this.socketServer) {
39
+ logger.debug("Initializing socket server");
29
40
  this.socketServer = IO.getSocketServer();
30
41
  logger.debug("Realtime socket server initialized");
31
- }
32
42
 
33
- this.socketServer!.on("connection", (socket: Socket) => {
34
- socket.on(EventName.ListenToModalEvent, async (data: JSONObject) => {
35
- // TODO: validate if this soocket has access to this tenant
36
-
37
- // TODO: validate if this socket has access to this model
38
-
39
- // TODO: validate if this socket has access to this event type
40
-
41
- // TODO: validate if this socket has access to this query
42
-
43
- // TODO: validate if this socket has access to this select
44
-
45
- // validate data
46
-
47
- if (typeof data["eventType"] !== "string") {
48
- throw new BadDataException("eventType is not a string");
49
- }
50
- if (typeof data["modelType"] !== "string") {
51
- throw new BadDataException("modelType is not a string");
52
- }
53
- if (typeof data["modelName"] !== "string") {
54
- throw new BadDataException("modelName is not a string");
55
- }
56
- if (typeof data["query"] !== "object") {
57
- throw new BadDataException("query is not an object");
58
- }
59
- if (typeof data["tenantId"] !== "string") {
60
- throw new BadDataException("tenantId is not a string");
61
- }
62
- if (typeof data["select"] !== "object") {
63
- throw new BadDataException("select is not an object");
64
- }
65
-
66
- await Realtime.listenToModelEvent(socket, {
67
- eventType: data["eventType"] as ModelEventType,
68
- modelType: data["modelType"] as DatabaseType,
69
- modelName: data["modelName"] as string,
70
- query: JSONFunctions.deserialize(data["query"] as JSONObject),
71
- tenantId: data["tenantId"] as string,
72
- select: JSONFunctions.deserialize(data["select"] as JSONObject),
43
+ this.socketServer!.on("connection", (socket: Socket) => {
44
+ logger.debug("New socket connection established");
45
+
46
+ socket.on(EventName.ListenToModalEvent, async (data: JSONObject) => {
47
+ logger.debug("Received ListenToModalEvent with data:");
48
+ logger.debug(data);
49
+
50
+ if (typeof data["eventType"] !== "string") {
51
+ logger.error("eventType is not a string");
52
+ throw new BadDataException("eventType is not a string");
53
+ }
54
+ if (typeof data["modelType"] !== "string") {
55
+ logger.error("modelType is not a string");
56
+ throw new BadDataException("modelType is not a string");
57
+ }
58
+ if (typeof data["modelName"] !== "string") {
59
+ logger.error("modelName is not a string");
60
+ throw new BadDataException("modelName is not a string");
61
+ }
62
+ if (typeof data["tenantId"] !== "string") {
63
+ logger.error("tenantId is not a string");
64
+ throw new BadDataException("tenantId is not a string");
65
+ }
66
+
67
+ await Realtime.listenToModelEvent(socket, {
68
+ eventType: data["eventType"] as ModelEventType,
69
+ modelType: data["modelType"] as DatabaseType,
70
+ modelName: data["modelName"] as string,
71
+ tenantId: data["tenantId"] as string,
72
+ });
73
73
  });
74
74
  });
75
- });
75
+ }
76
76
 
77
77
  return this.socketServer;
78
78
  }
@@ -81,66 +81,275 @@ export default abstract class Realtime {
81
81
  socket: Socket,
82
82
  data: ListenToModelEventJSON,
83
83
  ): Promise<void> {
84
+ logger.debug("Listening to model event with data:");
85
+ logger.debug(data);
86
+
84
87
  if (!this.socketServer) {
88
+ logger.debug("Socket server not initialized, initializing now");
85
89
  await this.init();
86
90
  }
87
91
 
88
- const roomId: string = RealtimeUtil.getRoomId(
89
- data.tenantId,
90
- data.modelName,
91
- data.eventType,
92
- );
92
+ // before joining room check the user token and check if the user has access to this tenant
93
+ // and to this model and to this event type
94
+
95
+ logger.debug("Extracting user access token from socket");
96
+ const userAccessToken: string | undefined =
97
+ this.getAccessTokenFromSocket(socket);
98
+
99
+ if (!userAccessToken) {
100
+ logger.debug(
101
+ "User access token not found in socket, aborting joining room",
102
+ );
103
+ return;
104
+ }
105
+
106
+ logger.debug("Decoding user access token");
107
+ const userAuthorizationData: JSONWebTokenData =
108
+ JSONWebToken.decode(userAccessToken);
109
+
110
+ if (!userAuthorizationData) {
111
+ logger.debug(
112
+ "User authorization data not found in socket, aborting joining room",
113
+ );
114
+ return;
115
+ }
116
+
117
+ if (!userAuthorizationData.userId) {
118
+ logger.debug("User ID not found in socket, aborting joining room");
119
+ return;
120
+ }
121
+
122
+ logger.debug("Checking user access permissions");
123
+ let hasAccess: boolean = false;
124
+
125
+ if (userAuthorizationData.isMasterAdmin) {
126
+ logger.debug("User is a master admin, granting access");
127
+ hasAccess = true;
128
+ }
129
+
130
+ logger.debug("Fetching user global access permissions");
131
+ const userGlobalAccessPermission: UserGlobalAccessPermission | null =
132
+ await UserPermissionUtil.getUserGlobalAccessPermissionFromCache(
133
+ userAuthorizationData.userId,
134
+ );
135
+
136
+ // check if the user has access to this tenant
137
+ if (userGlobalAccessPermission && !hasAccess) {
138
+ logger.debug("Checking if user has access to the tenant");
139
+ const hasAccessToProjectId: boolean =
140
+ userGlobalAccessPermission.projectIds.some((projectId: ObjectID) => {
141
+ return projectId.toString() === data.tenantId.toString();
142
+ });
143
+
144
+ if (!hasAccessToProjectId) {
145
+ logger.debug(
146
+ "User does not have access to this tenant, aborting joining room",
147
+ );
148
+ return;
149
+ }
150
+
151
+ logger.debug("User has access to the tenant, checking model access");
152
+ const userId: ObjectID = new ObjectID(
153
+ userAuthorizationData.userId.toString(),
154
+ );
155
+ const projectId: ObjectID = new ObjectID(data.tenantId.toString());
156
+
157
+ // if it has the access to the tenant, check if it has access to the model
158
+ const userTenantAccessPermission: UserTenantAccessPermission | null =
159
+ await UserPermissionUtil.getUserTenantAccessPermissionFromCache(
160
+ userId,
161
+ projectId,
162
+ );
163
+
164
+ let databaseRequestType: DatabaseRequestType = DatabaseRequestType.Read;
165
+
166
+ if (data.eventType === ModelEventType.Create) {
167
+ logger.debug("Event type is Create, setting request type to Create");
168
+ databaseRequestType = DatabaseRequestType.Create;
169
+ }
170
+
171
+ if (data.eventType === ModelEventType.Update) {
172
+ logger.debug("Event type is Update, setting request type to Update");
173
+ databaseRequestType = DatabaseRequestType.Update;
174
+ }
175
+
176
+ if (data.eventType === ModelEventType.Delete) {
177
+ logger.debug("Event type is Delete, setting request type to Delete");
178
+ databaseRequestType = DatabaseRequestType.Delete;
179
+ }
180
+
181
+ // check if the user has access to this model
182
+ if (
183
+ userTenantAccessPermission &&
184
+ this.hasPermissionsByModelName(
185
+ userTenantAccessPermission,
186
+ data.modelName,
187
+ databaseRequestType,
188
+ )
189
+ ) {
190
+ logger.debug("User has access to the model, granting access");
191
+ hasAccess = true;
192
+ }
193
+ }
194
+
195
+ if (!hasAccess) {
196
+ logger.debug(
197
+ "User does not have access to this tenant, aborting joining room",
198
+ );
199
+ return;
200
+ }
201
+
202
+ if (data.modelId) {
203
+ const modelRoomId: string = RealtimeUtil.getRoomId(
204
+ data.tenantId,
205
+ data.modelName,
206
+ ModelEventType.Create,
207
+ data.modelId,
208
+ );
93
209
 
94
- // join the room.
95
- await socket.join(roomId);
210
+ logger.debug(`Joining room with ID: ${modelRoomId}`);
211
+ // join the room.
212
+ await socket.join(modelRoomId);
213
+ } else {
214
+ const roomId: string = RealtimeUtil.getRoomId(
215
+ data.tenantId,
216
+ data.modelName,
217
+ data.eventType,
218
+ );
219
+
220
+ logger.debug(`Joining room with ID: ${roomId}`);
221
+ // join the room.
222
+ await socket.join(roomId);
223
+ }
96
224
  }
97
225
 
98
226
  public static async stopListeningToModelEvent(
99
227
  socket: Socket,
100
228
  data: ListenToModelEventJSON,
101
229
  ): Promise<void> {
230
+ logger.debug("Stopping listening to model event with data:");
231
+ logger.debug(data);
232
+
102
233
  if (!this.socketServer) {
234
+ logger.debug("Socket server not initialized, initializing now");
103
235
  await this.init();
104
236
  }
105
237
 
106
- // leave this room.
107
- await socket.leave(
108
- RealtimeUtil.getRoomId(data.tenantId, data.modelName, data.eventType),
238
+ const roomId: string = RealtimeUtil.getRoomId(
239
+ data.tenantId,
240
+ data.modelName,
241
+ data.eventType,
242
+ data.modelId,
109
243
  );
244
+
245
+ logger.debug(`Leaving room with ID: ${roomId}`);
246
+ // leave this room.
247
+ await socket.leave(roomId);
110
248
  }
111
249
 
112
250
  public static async emitModelEvent(data: {
113
251
  tenantId: string | ObjectID;
114
252
  eventType: ModelEventType;
115
- model: BaseModel | AnalyticsBaseModel;
253
+ modelId: ObjectID;
116
254
  modelType: { new (): BaseModel | AnalyticsBaseModel };
117
255
  }): Promise<void> {
256
+ logger.debug("Emitting model event with data:");
257
+ logger.debug(`Tenant ID: ${data.tenantId}`);
258
+ logger.debug(`Event Type: ${data.eventType}`);
259
+ logger.debug(`Model ID: ${data.modelId}`);
260
+
118
261
  if (!this.socketServer) {
262
+ logger.debug("Socket server not initialized, initializing now");
119
263
  await this.init();
120
264
  }
121
265
 
122
- let jsonObject: JSONObject = {};
266
+ const jsonObject: JSONObject = {
267
+ modelId: data.modelId.toString(),
268
+ };
123
269
 
124
- if (data.model instanceof BaseModel) {
125
- jsonObject = BaseModel.toJSON(
126
- data.model,
127
- data.modelType as DatabaseBaseModelType,
128
- );
129
- }
270
+ const model: BaseModel | AnalyticsBaseModel = new data.modelType();
130
271
 
131
- if (data.model instanceof AnalyticsBaseModel) {
132
- jsonObject = AnalyticsBaseModel.toJSON(
133
- data.model,
134
- data.modelType as AnalyticsBaseModelType,
135
- );
272
+ if (!model.tableName) {
273
+ logger.warn("Model does not have a tableName, aborting emit");
274
+ return;
136
275
  }
137
276
 
138
277
  const roomId: string = RealtimeUtil.getRoomId(
139
278
  data.tenantId,
140
- data.model.tableName!,
279
+ model.tableName!,
141
280
  data.eventType,
142
281
  );
143
282
 
283
+ const modelRoomId: string = RealtimeUtil.getRoomId(
284
+ data.tenantId,
285
+ model.tableName!,
286
+ ModelEventType.Create,
287
+ data.modelId,
288
+ );
289
+
290
+ logger.debug(`Emitting event to room with ID: ${roomId}`);
291
+ logger.debug(jsonObject);
292
+
144
293
  this.socketServer!.to(roomId).emit(roomId, jsonObject);
294
+ this.socketServer!.to(modelRoomId).emit(modelRoomId, jsonObject);
295
+ }
296
+
297
+ public static hasPermissionsByModelName(
298
+ userProjectPermissions: UserTenantAccessPermission | Array<Permission>,
299
+ modelName: string,
300
+ requestType: DatabaseRequestType,
301
+ ): boolean {
302
+ let modelPermissions: Array<Permission> = [];
303
+
304
+ let modelType:
305
+ | { new (): BaseModel }
306
+ | { new (): AnalyticsBaseModel }
307
+ | null = getModelTypeByName(modelName);
308
+
309
+ if (!modelType) {
310
+ // check if it is an analytics model
311
+ modelType = getAnalyticsModelTypeByname(modelName);
312
+
313
+ if (!modelType) {
314
+ return false;
315
+ }
316
+ }
317
+
318
+ if (requestType === DatabaseRequestType.Create) {
319
+ modelPermissions = new modelType().getCreatePermissions();
320
+ }
321
+
322
+ if (requestType === DatabaseRequestType.Read) {
323
+ modelPermissions = new modelType().getReadPermissions();
324
+ }
325
+
326
+ if (requestType === DatabaseRequestType.Update) {
327
+ modelPermissions = new modelType().getUpdatePermissions();
328
+ }
329
+
330
+ if (requestType === DatabaseRequestType.Delete) {
331
+ modelPermissions = new modelType().getDeletePermissions();
332
+ }
333
+
334
+ return ModelPermission.hasPermissions(
335
+ userProjectPermissions,
336
+ modelPermissions,
337
+ );
338
+ }
339
+
340
+ public static getAccessTokenFromSocket(socket: Socket): string | undefined {
341
+ let accessToken: string | undefined = undefined;
342
+
343
+ if (socket.handshake.headers.cookie) {
344
+ const cookies: Dictionary<string> = CookieUtil.getCookiesFromCookieString(
345
+ socket.handshake.headers.cookie,
346
+ );
347
+
348
+ if (cookies[CookieUtil.getUserTokenKey()]) {
349
+ accessToken = cookies[CookieUtil.getUserTokenKey()];
350
+ }
351
+ }
352
+
353
+ return accessToken;
145
354
  }
146
355
  }