@oneuptime/common 12.0.18 → 12.0.19

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 (206) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  20. package/Server/Services/Index.ts +6 -2
  21. package/Server/Services/LlmLogService.ts +25 -9
  22. package/Server/Services/OnCallReadinessService.ts +86 -2
  23. package/Server/Services/ProjectService.ts +67 -0
  24. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  25. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  26. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  27. package/Server/Services/UserNotificationRuleService.ts +589 -11
  28. package/Server/Services/UserNotificationSettingService.ts +138 -0
  29. package/Server/Services/UserService.ts +28 -0
  30. package/Server/Services/UserSlackService.ts +218 -0
  31. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  32. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  33. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  34. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  35. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  36. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  37. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  38. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  39. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  40. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  41. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  42. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  43. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  44. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  45. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  46. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  47. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  48. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  49. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  50. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  51. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  52. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  53. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  54. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  55. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  56. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  57. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  58. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  59. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  60. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  61. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  62. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  63. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  64. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  65. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  66. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  67. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  68. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  69. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  70. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  71. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  72. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  73. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  74. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  75. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  76. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  77. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  78. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  79. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  80. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  81. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  83. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  84. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  85. package/Types/Marketing/MarketingEvent.ts +97 -0
  86. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  87. package/UI/Utils/PermissionGate.ts +56 -0
  88. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  89. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  91. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  93. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  95. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  96. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  97. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  99. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  101. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  103. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  104. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  105. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  106. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  107. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  108. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  109. package/build/dist/Server/EnvironmentConfig.js +10 -70
  110. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  122. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  123. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  124. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  125. package/build/dist/Server/Services/Index.js +6 -2
  126. package/build/dist/Server/Services/Index.js.map +1 -1
  127. package/build/dist/Server/Services/LlmLogService.js +25 -9
  128. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  129. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  130. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  131. package/build/dist/Server/Services/ProjectService.js +56 -0
  132. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  133. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  134. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  135. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  136. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  137. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  138. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  139. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  140. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  141. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  142. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  143. package/build/dist/Server/Services/UserService.js +26 -1
  144. package/build/dist/Server/Services/UserService.js.map +1 -1
  145. package/build/dist/Server/Services/UserSlackService.js +195 -0
  146. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  147. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  148. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  149. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  150. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  151. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  152. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  153. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  154. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  155. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  156. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  157. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  158. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  160. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  161. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  162. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  163. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  164. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  165. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  166. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  167. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  168. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  169. package/package.json +1 -1
  170. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  171. package/Server/Services/MarketingConversionService.ts +0 -10
  172. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  173. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  174. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  175. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  176. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  177. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  178. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  179. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  180. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  181. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  182. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  183. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  184. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  185. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  186. package/Types/Marketing/MarketingConversion.ts +0 -53
  187. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  188. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  189. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  190. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  191. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  192. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  193. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  194. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  195. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  196. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  197. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  198. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  199. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  200. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  201. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  202. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  203. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  204. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  205. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  206. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -8,6 +8,7 @@ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
8
8
  import TableColumn from "../../Types/Database/TableColumn";
9
9
  import TableColumnType from "../../Types/Database/TableColumnType";
10
10
  import TableMetadata from "../../Types/Database/TableMetadata";
11
+ import Email from "../../Types/Email";
11
12
  import IconProp from "../../Types/Icon/IconProp";
12
13
  import { Column, Entity, Index } from "typeorm";
13
14
 
@@ -47,6 +48,28 @@ export default class EnterpriseLicense extends BaseModel {
47
48
  })
48
49
  public companyName?: string = undefined;
49
50
 
51
+ @ColumnAccessControl({
52
+ create: [],
53
+ read: [],
54
+ update: [],
55
+ })
56
+ @TableColumn({
57
+ required: false,
58
+ type: TableColumnType.Email,
59
+ title: "Email",
60
+ description:
61
+ "Contact email for this license. Set it to the address the customer booked their meeting with, so the licence can be joined to the conversion that produced it.",
62
+ })
63
+ @Index("idx_enterprise_license_email")
64
+ @Column({
65
+ nullable: true,
66
+ type: ColumnType.Email,
67
+ length: ColumnLength.Email,
68
+ unique: false,
69
+ transformer: Email.getDatabaseTransformer(),
70
+ })
71
+ public email?: Email = undefined;
72
+
50
73
  @ColumnAccessControl({
51
74
  create: [],
52
75
  read: [],
@@ -278,6 +278,8 @@ import UserEmail from "./UserEmail";
278
278
  import UserPush from "./UserPush";
279
279
  import UserWhatsApp from "./UserWhatsApp";
280
280
  import UserTelegram from "./UserTelegram";
281
+ import UserSlack from "./UserSlack";
282
+ import UserMicrosoftTeams from "./UserMicrosoftTeams";
281
283
  import UserWebhook from "./UserWebhook";
282
284
  // User Notification Rules
283
285
  import UserNotificationRule from "./UserNotificationRule";
@@ -437,7 +439,6 @@ import MetricType from "./MetricType";
437
439
  import ProjectSCIM from "./ProjectSCIM";
438
440
  import ProjectSCIMLog from "./ProjectSCIMLog";
439
441
  import StatusPageSCIMLog from "./StatusPageSCIMLog";
440
- import MarketingConversion from "./MarketingConversion";
441
442
  import DeletedProject from "./DeletedProject";
442
443
 
443
444
  const AllModelTypes: Array<{
@@ -703,6 +704,8 @@ const AllModelTypes: Array<{
703
704
  UserPush,
704
705
  UserWhatsApp,
705
706
  UserTelegram,
707
+ UserSlack,
708
+ UserMicrosoftTeams,
706
709
  UserWebhook,
707
710
  UserIncomingCallNumber,
708
711
 
@@ -897,7 +900,6 @@ const AllModelTypes: Array<{
897
900
  RumSessionReplayView,
898
901
  RumSessionErasureRequest,
899
902
  RumSessionPin,
900
- MarketingConversion,
901
903
  DeletedProject,
902
904
  ];
903
905
 
@@ -1220,15 +1220,25 @@ export default class Monitor extends BaseModel {
1220
1220
 
1221
1221
  @ColumnAccessControl({
1222
1222
  create: [],
1223
+ /*
1224
+ * Gated on the ability to ROTATE the secret, not on the ability to view
1225
+ * the monitor: this read list is deliberately identical to the update list
1226
+ * below. A holder of this key can authenticate as any host
1227
+ * agent reporting to the monitor -- forging or suppressing heartbeats.
1228
+ *
1229
+ * Viewer, MonitorViewer and ReadProjectMonitor used to be here. Viewer is
1230
+ * the least privilege OneUptime grants and is what gets handed to
1231
+ * dashboards, auditors, contractors and automations, so "read-only" also
1232
+ * meant "can read a live bearer credential".
1233
+ * https://github.com/OneUptime/oneuptime/issues/3360
1234
+ */
1223
1235
  read: [
1224
1236
  Permission.ProjectOwner,
1225
1237
  Permission.ProjectAdmin,
1226
1238
  Permission.ProjectMember,
1227
- Permission.Viewer,
1228
1239
  Permission.MonitorAdmin,
1229
1240
  Permission.MonitorMember,
1230
- Permission.MonitorViewer,
1231
- Permission.ReadProjectMonitor,
1241
+ Permission.EditProjectMonitor,
1232
1242
  ],
1233
1243
  update: [
1234
1244
  Permission.ProjectOwner,
@@ -1258,15 +1268,25 @@ export default class Monitor extends BaseModel {
1258
1268
 
1259
1269
  @ColumnAccessControl({
1260
1270
  create: [],
1271
+ /*
1272
+ * Gated on the ability to ROTATE the secret, not on the ability to view
1273
+ * the monitor: this read list is deliberately identical to the update list
1274
+ * below. This key IS the heartbeat URL: a holder can beat the
1275
+ * monitor and keep a dead service looking alive.
1276
+ *
1277
+ * Viewer, MonitorViewer and ReadProjectMonitor used to be here. Viewer is
1278
+ * the least privilege OneUptime grants and is what gets handed to
1279
+ * dashboards, auditors, contractors and automations, so "read-only" also
1280
+ * meant "can read a live bearer credential".
1281
+ * https://github.com/OneUptime/oneuptime/issues/3360
1282
+ */
1261
1283
  read: [
1262
1284
  Permission.ProjectOwner,
1263
1285
  Permission.ProjectAdmin,
1264
1286
  Permission.ProjectMember,
1265
- Permission.Viewer,
1266
1287
  Permission.MonitorAdmin,
1267
1288
  Permission.MonitorMember,
1268
- Permission.MonitorViewer,
1269
- Permission.ReadProjectMonitor,
1289
+ Permission.EditProjectMonitor,
1270
1290
  ],
1271
1291
  update: [
1272
1292
  Permission.ProjectOwner,
@@ -1333,15 +1353,25 @@ export default class Monitor extends BaseModel {
1333
1353
 
1334
1354
  @ColumnAccessControl({
1335
1355
  create: [],
1356
+ /*
1357
+ * Gated on the ability to ROTATE the secret, not on the ability to view
1358
+ * the monitor: this read list is deliberately identical to the update list
1359
+ * below. This key IS the monitor's inbound address: a holder can
1360
+ * send mail that the monitor counts as a heartbeat.
1361
+ *
1362
+ * Viewer, MonitorViewer and ReadProjectMonitor used to be here. Viewer is
1363
+ * the least privilege OneUptime grants and is what gets handed to
1364
+ * dashboards, auditors, contractors and automations, so "read-only" also
1365
+ * meant "can read a live bearer credential".
1366
+ * https://github.com/OneUptime/oneuptime/issues/3360
1367
+ */
1336
1368
  read: [
1337
1369
  Permission.ProjectOwner,
1338
1370
  Permission.ProjectAdmin,
1339
1371
  Permission.ProjectMember,
1340
- Permission.Viewer,
1341
1372
  Permission.MonitorAdmin,
1342
1373
  Permission.MonitorMember,
1343
- Permission.MonitorViewer,
1344
- Permission.ReadProjectMonitor,
1374
+ Permission.EditProjectMonitor,
1345
1375
  ],
1346
1376
  update: [
1347
1377
  Permission.ProjectOwner,
@@ -0,0 +1,344 @@
1
+ import Project from "./Project";
2
+ import User from "./User";
3
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
4
+ import Route from "../../Types/API/Route";
5
+ import AllowAccessIfSubscriptionIsUnpaid from "../../Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid";
6
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
7
+ import OwnerOnlyColumn from "../../Types/Database/AccessControl/OwnerOnlyColumn";
8
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
9
+ import ColumnLength from "../../Types/Database/ColumnLength";
10
+ import ColumnType from "../../Types/Database/ColumnType";
11
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
12
+ import CurrentUserCanAccessRecordBy from "../../Types/Database/CurrentUserCanAccessRecordBy";
13
+ import TableColumn from "../../Types/Database/TableColumn";
14
+ import TableColumnType from "../../Types/Database/TableColumnType";
15
+ import TableMetadata from "../../Types/Database/TableMetadata";
16
+ import TenantColumn from "../../Types/Database/TenantColumn";
17
+ import IconProp from "../../Types/Icon/IconProp";
18
+ import ObjectID from "../../Types/ObjectID";
19
+ import Permission from "../../Types/Permission";
20
+ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
21
+
22
+ /*
23
+ * `read` names Permission.CurrentUser and nothing else, exactly like the other
24
+ * notification-method models (UserTelegram has the full essay). That single
25
+ * entry is what keeps every row owner-only: row scoping comes from the TABLE
26
+ * list alone, and widening it would expose the Microsoft Entra user id every
27
+ * project member's pages are delivered to. Do not add administrator
28
+ * permissions here - OnCallReadinessService returns the masked,
29
+ * admin-sanctioned view instead.
30
+ *
31
+ * Unlike Telegram/SMS/WhatsApp there is no verification code: a row is only
32
+ * ever created from an existing, OAuth-established workspace link
33
+ * (WorkspaceUserAuthToken), so creation IS verification.
34
+ * UserMicrosoftTeamsService enforces that and stamps the Microsoft Teams
35
+ * identifiers server-side.
36
+ */
37
+ @TenantColumn("projectId")
38
+ @AllowAccessIfSubscriptionIsUnpaid()
39
+ @TableAccessControl({
40
+ create: [Permission.CurrentUser],
41
+ read: [Permission.CurrentUser],
42
+ delete: [Permission.CurrentUser],
43
+ update: [Permission.CurrentUser],
44
+ })
45
+ @CrudApiEndpoint(new Route("/user-microsoft-teams"))
46
+ @Entity({
47
+ name: "UserMicrosoftTeams",
48
+ })
49
+ @TableMetadata({
50
+ tableName: "UserMicrosoftTeams",
51
+ singularName: "Microsoft Teams Account",
52
+ pluralName: "Microsoft Teams Accounts",
53
+ icon: IconProp.MicrosoftTeams,
54
+ tableDescription:
55
+ "Microsoft Teams accounts used for Microsoft Teams notifications.",
56
+ })
57
+ @CurrentUserCanAccessRecordBy("userId")
58
+ class UserMicrosoftTeams extends BaseModel {
59
+ @ColumnAccessControl({
60
+ create: [Permission.CurrentUser],
61
+ read: [Permission.CurrentUser],
62
+ update: [],
63
+ })
64
+ @TableColumn({
65
+ manyToOneRelationColumn: "projectId",
66
+ type: TableColumnType.Entity,
67
+ modelType: Project,
68
+ title: "Project",
69
+ description: "Relation to Project Resource in which this object belongs",
70
+ })
71
+ @ManyToOne(
72
+ () => {
73
+ return Project;
74
+ },
75
+ {
76
+ eager: false,
77
+ nullable: true,
78
+ onDelete: "CASCADE",
79
+ orphanedRowAction: "nullify",
80
+ },
81
+ )
82
+ @JoinColumn({ name: "projectId" })
83
+ public project?: Project = undefined;
84
+
85
+ @ColumnAccessControl({
86
+ create: [Permission.CurrentUser],
87
+ read: [Permission.CurrentUser],
88
+ update: [],
89
+ })
90
+ @Index()
91
+ @TableColumn({
92
+ type: TableColumnType.ObjectID,
93
+ required: true,
94
+ canReadOnRelationQuery: true,
95
+ title: "Project ID",
96
+ description: "ID of your OneUptime Project in which this object belongs",
97
+ })
98
+ @Column({
99
+ type: ColumnType.ObjectID,
100
+ nullable: false,
101
+ transformer: ObjectID.getDatabaseTransformer(),
102
+ })
103
+ public projectId?: ObjectID = undefined;
104
+
105
+ @ColumnAccessControl({
106
+ /*
107
+ * CurrentUser here does NOT mean the client may choose the value: the
108
+ * service hook refuses any non-root payload that carries these columns
109
+ * and then stamps them itself, and hooks run BEFORE the column
110
+ * permission check (DatabaseService.create). The entry exists because
111
+ * that same check runs on the STAMPED data - with an empty list the
112
+ * server's own write is refused and no user can add this method at all.
113
+ * Same shape as UserWebAuthn's server-stamped isVerified.
114
+ */
115
+ create: [Permission.CurrentUser],
116
+ read: [Permission.CurrentUser],
117
+ update: [],
118
+ })
119
+ @Index()
120
+ /*
121
+ * The Microsoft Entra (Azure AD) object id the project's bot delivers
122
+ * direct messages to. Holding it is enough to address bot messages at that
123
+ * person, so it is a delivery target and not a label. Captured server-side
124
+ * from the user's own WorkspaceUserAuthToken - never writable by the client.
125
+ */
126
+ @OwnerOnlyColumn()
127
+ @TableColumn({
128
+ title: "Microsoft Teams User ID",
129
+ required: false,
130
+ unique: false,
131
+ type: TableColumnType.ShortText,
132
+ canReadOnRelationQuery: true,
133
+ description:
134
+ "Microsoft Entra user ID captured from your connected Microsoft Teams account. Populated automatically.",
135
+ })
136
+ @Column({
137
+ type: ColumnType.ShortText,
138
+ length: ColumnLength.ShortText,
139
+ unique: false,
140
+ nullable: true,
141
+ })
142
+ public microsoftTeamsUserId?: string = undefined;
143
+
144
+ @ColumnAccessControl({
145
+ /*
146
+ * CurrentUser here does NOT mean the client may choose the value: the
147
+ * service hook refuses any non-root payload that carries these columns
148
+ * and then stamps them itself, and hooks run BEFORE the column
149
+ * permission check (DatabaseService.create). The entry exists because
150
+ * that same check runs on the STAMPED data - with an empty list the
151
+ * server's own write is refused and no user can add this method at all.
152
+ * Same shape as UserWebAuthn's server-stamped isVerified.
153
+ */
154
+ create: [Permission.CurrentUser],
155
+ read: [Permission.CurrentUser],
156
+ update: [],
157
+ })
158
+ /*
159
+ * The person's Microsoft Teams display name at the time the method was
160
+ * added. Only a label for the row, but it is still their identity in an
161
+ * outside workspace, so it stays owner-only like the Telegram handle.
162
+ */
163
+ @OwnerOnlyColumn()
164
+ @TableColumn({
165
+ title: "Microsoft Teams Username",
166
+ required: false,
167
+ unique: false,
168
+ type: TableColumnType.ShortText,
169
+ canReadOnRelationQuery: true,
170
+ description:
171
+ "Display name captured from your connected Microsoft Teams account. Populated automatically.",
172
+ })
173
+ @Column({
174
+ type: ColumnType.ShortText,
175
+ length: ColumnLength.ShortText,
176
+ unique: false,
177
+ nullable: true,
178
+ })
179
+ public microsoftTeamsUserName?: string = undefined;
180
+
181
+ @ColumnAccessControl({
182
+ create: [Permission.CurrentUser],
183
+ read: [Permission.CurrentUser],
184
+ update: [],
185
+ })
186
+ @TableColumn({
187
+ manyToOneRelationColumn: "user",
188
+ type: TableColumnType.Entity,
189
+ modelType: User,
190
+ title: "User",
191
+ description: "Relation to User who this Microsoft Teams account belongs to",
192
+ })
193
+ @ManyToOne(
194
+ () => {
195
+ return User;
196
+ },
197
+ {
198
+ eager: false,
199
+ nullable: true,
200
+ onDelete: "CASCADE",
201
+ orphanedRowAction: "nullify",
202
+ },
203
+ )
204
+ @JoinColumn({ name: "userId" })
205
+ public user?: User = undefined;
206
+
207
+ @ColumnAccessControl({
208
+ create: [Permission.CurrentUser],
209
+ read: [Permission.CurrentUser],
210
+ update: [],
211
+ })
212
+ @TableColumn({
213
+ type: TableColumnType.ObjectID,
214
+ title: "User ID",
215
+ description: "User ID who this Microsoft Teams account belongs to",
216
+ })
217
+ @Column({
218
+ type: ColumnType.ObjectID,
219
+ nullable: true,
220
+ transformer: ObjectID.getDatabaseTransformer(),
221
+ })
222
+ @Index()
223
+ public userId?: ObjectID = undefined;
224
+
225
+ @ColumnAccessControl({
226
+ create: [Permission.CurrentUser],
227
+ read: [Permission.CurrentUser],
228
+ update: [],
229
+ })
230
+ @TableColumn({
231
+ manyToOneRelationColumn: "createdByUserId",
232
+ type: TableColumnType.Entity,
233
+ modelType: User,
234
+ title: "Created by User",
235
+ description:
236
+ "Relation to User who created this object (if this object was created by a User)",
237
+ })
238
+ @ManyToOne(
239
+ () => {
240
+ return User;
241
+ },
242
+ {
243
+ eager: false,
244
+ nullable: true,
245
+ onDelete: "SET NULL",
246
+ orphanedRowAction: "nullify",
247
+ },
248
+ )
249
+ @JoinColumn({ name: "createdByUserId" })
250
+ public createdByUser?: User = undefined;
251
+
252
+ @ColumnAccessControl({
253
+ create: [Permission.CurrentUser],
254
+ read: [Permission.CurrentUser],
255
+ update: [],
256
+ })
257
+ @TableColumn({
258
+ type: TableColumnType.ObjectID,
259
+ title: "Created by User ID",
260
+ description:
261
+ "User ID who created this object (if this object was created by a User)",
262
+ })
263
+ @Column({
264
+ type: ColumnType.ObjectID,
265
+ nullable: true,
266
+ transformer: ObjectID.getDatabaseTransformer(),
267
+ })
268
+ public createdByUserId?: ObjectID = undefined;
269
+
270
+ @ColumnAccessControl({
271
+ create: [],
272
+ read: [],
273
+ update: [],
274
+ })
275
+ @TableColumn({
276
+ manyToOneRelationColumn: "deletedByUserId",
277
+ type: TableColumnType.Entity,
278
+ title: "Deleted by User",
279
+ modelType: User,
280
+ description:
281
+ "Relation to User who deleted this object (if this object was deleted by a User)",
282
+ })
283
+ @ManyToOne(
284
+ () => {
285
+ return User;
286
+ },
287
+ {
288
+ cascade: false,
289
+ eager: false,
290
+ nullable: true,
291
+ onDelete: "SET NULL",
292
+ orphanedRowAction: "nullify",
293
+ },
294
+ )
295
+ @JoinColumn({ name: "deletedByUserId" })
296
+ public deletedByUser?: User = undefined;
297
+
298
+ @ColumnAccessControl({
299
+ create: [],
300
+ read: [],
301
+ update: [],
302
+ })
303
+ @TableColumn({
304
+ type: TableColumnType.ObjectID,
305
+ title: "Deleted by User ID",
306
+ description:
307
+ "User ID who deleted this object (if this object was deleted by a User)",
308
+ })
309
+ @Column({
310
+ type: ColumnType.ObjectID,
311
+ nullable: true,
312
+ transformer: ObjectID.getDatabaseTransformer(),
313
+ })
314
+ public deletedByUserId?: ObjectID = undefined;
315
+
316
+ @ColumnAccessControl({
317
+ /*
318
+ * CurrentUser here does NOT mean the client may choose the value: the
319
+ * service hook refuses any non-root payload that carries these columns
320
+ * and then stamps them itself, and hooks run BEFORE the column
321
+ * permission check (DatabaseService.create). The entry exists because
322
+ * that same check runs on the STAMPED data - with an empty list the
323
+ * server's own write is refused and no user can add this method at all.
324
+ * Same shape as UserWebAuthn's server-stamped isVerified.
325
+ */
326
+ create: [Permission.CurrentUser],
327
+ read: [Permission.CurrentUser],
328
+ update: [],
329
+ })
330
+ @TableColumn({
331
+ title: "Is Verified",
332
+ description: "Is this Microsoft Teams account verified?",
333
+ isDefaultValueColumn: true,
334
+ type: TableColumnType.Boolean,
335
+ defaultValue: false,
336
+ })
337
+ @Column({
338
+ type: ColumnType.Boolean,
339
+ default: false,
340
+ })
341
+ public isVerified?: boolean = undefined;
342
+ }
343
+
344
+ export default UserMicrosoftTeams;
@@ -7,6 +7,8 @@ import UserEmail from "./UserEmail";
7
7
  import UserPush from "./UserPush";
8
8
  import UserSMS from "./UserSMS";
9
9
  import UserTelegram from "./UserTelegram";
10
+ import UserSlack from "./UserSlack";
11
+ import UserMicrosoftTeams from "./UserMicrosoftTeams";
10
12
  import UserWebhook from "./UserWebhook";
11
13
  import UserWhatsApp from "./UserWhatsApp";
12
14
  import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
@@ -80,9 +82,9 @@ const ADMIN_WRITE_PERMISSIONS: Array<Permission> = [
80
82
  * only EditProjectUserNotificationRule would leave the feature dead on arrival
81
83
  * for every project that already exists.
82
84
  *
83
- * Note the asymmetry with the seven notification-method models this rule points
85
+ * Note the asymmetry with the nine notification-method models this rule points
84
86
  * at (UserEmail, UserSMS, UserCall, UserPush, UserWhatsApp, UserTelegram,
85
- * UserWebhook). There, only `read` is widened. An administrator may see which
87
+ * UserSlack, UserMicrosoftTeams, UserWebhook). There, only `read` is widened. An administrator may see which
86
88
  * methods a member has and repair the rules that route to them, but may never
87
89
  * create or edit a method on somebody else's behalf. Letting them would hand
88
90
  * any administrator a paging-hijack primitive - add a phone number or webhook
@@ -382,11 +384,11 @@ class UserNotificationRule extends BaseModel {
382
384
  public deletedByUserId?: ObjectID = undefined;
383
385
 
384
386
  /*
385
- * THE SEVEN METHOD RELATIONS BELOW ARE THE MOST DANGEROUS COLUMNS ON THIS
387
+ * THE NINE METHOD RELATIONS BELOW ARE THE MOST DANGEROUS COLUMNS ON THIS
386
388
  * MODEL. What follows applies to every one of them - userCall, userPush,
387
- * userSms, userWhatsApp, userTelegram, userWebhook and userEmail, in both
388
- * their relation and their `*Id` spelling - and is written out once here
389
- * rather than fourteen times.
389
+ * userSms, userWhatsApp, userTelegram, userSlack, userMicrosoftTeams,
390
+ * userWebhook and userEmail, in both their relation and their `*Id`
391
+ * spelling - and is written out once here rather than eighteen times.
390
392
  *
391
393
  * A rule is a pair: the ownership column decides WHOSE pages select the row,
392
394
  * and the method relation decides WHERE those pages are delivered. The two
@@ -751,6 +753,139 @@ class UserNotificationRule extends BaseModel {
751
753
  })
752
754
  public userTelegramId?: ObjectID = undefined;
753
755
 
756
+ /*
757
+ * READ IS OWNER-ONLY ON THE RELATION, EVEN THOUGH THIS TABLE IS ADMIN-READABLE.
758
+ *
759
+ * Widening the relation would re-open the exposure that reverting the
760
+ * method models was meant to close, by a different route. A read of this
761
+ * table is NOT pinned to the caller's own rows for an administrator - that is
762
+ * the whole point of the phase - and QueryPermission.checkRelationQueryPermission
763
+ * authorises each nested field with relatedModel.hasReadPermissions, a
764
+ * name-intersection in which Permission.CurrentUser is auto-granted to every
765
+ * authenticated caller. So `select: { userSlack: { slackUserId: true } }` on somebody
766
+ * else's rule would hand back their raw Slack member id with no row scope
767
+ * anywhere in the path.
768
+ *
769
+ * The FK below stays admin-readable: an id is not sensitive, and it is what
770
+ * correlates a rule with the masked entry in OnCallReadinessService's
771
+ * ReadinessMethod { methodId, methodType, maskedIdentifier, isVerified }.
772
+ * That masked payload is the administrator's sanctioned view of a method.
773
+ */
774
+ @ColumnAccessControl({
775
+ create: [Permission.CurrentUser, ...ADMIN_WRITE_PERMISSIONS],
776
+ read: [Permission.CurrentUser],
777
+ update: [],
778
+ })
779
+ @TableColumn({
780
+ manyToOneRelationColumn: "userSlackId",
781
+ type: TableColumnType.Entity,
782
+ modelType: UserSlack,
783
+ title: "User Slack",
784
+ description: "Relation to User Slack Resource in which this object belongs",
785
+ })
786
+ @ManyToOne(
787
+ () => {
788
+ return UserSlack;
789
+ },
790
+ {
791
+ eager: false,
792
+ nullable: true,
793
+ onDelete: "CASCADE",
794
+ orphanedRowAction: "nullify",
795
+ },
796
+ )
797
+ @JoinColumn({ name: "userSlackId" })
798
+ public userSlack?: UserSlack = undefined;
799
+
800
+ // Re-pointable by an administrator. Read the essay above `userCall` first.
801
+ @ColumnAccessControl({
802
+ create: [Permission.CurrentUser, ...ADMIN_WRITE_PERMISSIONS],
803
+ read: [Permission.CurrentUser, ...ADMIN_READ_PERMISSIONS],
804
+ update: [...ADMIN_WRITE_PERMISSIONS],
805
+ })
806
+ @Index()
807
+ @TableColumn({
808
+ type: TableColumnType.ObjectID,
809
+ required: false,
810
+ canReadOnRelationQuery: true,
811
+ title: "User Slack ID",
812
+ description: "ID of User Slack in which this object belongs",
813
+ example: "4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
814
+ })
815
+ @Column({
816
+ type: ColumnType.ObjectID,
817
+ nullable: true,
818
+ transformer: ObjectID.getDatabaseTransformer(),
819
+ })
820
+ public userSlackId?: ObjectID = undefined;
821
+
822
+ /*
823
+ * READ IS OWNER-ONLY ON THE RELATION, EVEN THOUGH THIS TABLE IS ADMIN-READABLE.
824
+ *
825
+ * Widening the relation would re-open the exposure that reverting the
826
+ * method models was meant to close, by a different route. A read of this
827
+ * table is NOT pinned to the caller's own rows for an administrator - that is
828
+ * the whole point of the phase - and QueryPermission.checkRelationQueryPermission
829
+ * authorises each nested field with relatedModel.hasReadPermissions, a
830
+ * name-intersection in which Permission.CurrentUser is auto-granted to every
831
+ * authenticated caller. So `select: { userMicrosoftTeams: { microsoftTeamsUserId: true } }`
832
+ * on somebody else's rule would hand back their raw Microsoft Entra user id
833
+ * with no row scope anywhere in the path.
834
+ *
835
+ * The FK below stays admin-readable: an id is not sensitive, and it is what
836
+ * correlates a rule with the masked entry in OnCallReadinessService's
837
+ * ReadinessMethod { methodId, methodType, maskedIdentifier, isVerified }.
838
+ * That masked payload is the administrator's sanctioned view of a method.
839
+ */
840
+ @ColumnAccessControl({
841
+ create: [Permission.CurrentUser, ...ADMIN_WRITE_PERMISSIONS],
842
+ read: [Permission.CurrentUser],
843
+ update: [],
844
+ })
845
+ @TableColumn({
846
+ manyToOneRelationColumn: "userMicrosoftTeamsId",
847
+ type: TableColumnType.Entity,
848
+ modelType: UserMicrosoftTeams,
849
+ title: "User Microsoft Teams",
850
+ description:
851
+ "Relation to User Microsoft Teams Resource in which this object belongs",
852
+ })
853
+ @ManyToOne(
854
+ () => {
855
+ return UserMicrosoftTeams;
856
+ },
857
+ {
858
+ eager: false,
859
+ nullable: true,
860
+ onDelete: "CASCADE",
861
+ orphanedRowAction: "nullify",
862
+ },
863
+ )
864
+ @JoinColumn({ name: "userMicrosoftTeamsId" })
865
+ public userMicrosoftTeams?: UserMicrosoftTeams = undefined;
866
+
867
+ // Re-pointable by an administrator. Read the essay above `userCall` first.
868
+ @ColumnAccessControl({
869
+ create: [Permission.CurrentUser, ...ADMIN_WRITE_PERMISSIONS],
870
+ read: [Permission.CurrentUser, ...ADMIN_READ_PERMISSIONS],
871
+ update: [...ADMIN_WRITE_PERMISSIONS],
872
+ })
873
+ @Index()
874
+ @TableColumn({
875
+ type: TableColumnType.ObjectID,
876
+ required: false,
877
+ canReadOnRelationQuery: true,
878
+ title: "User Microsoft Teams ID",
879
+ description: "ID of User Microsoft Teams in which this object belongs",
880
+ example: "5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b",
881
+ })
882
+ @Column({
883
+ type: ColumnType.ObjectID,
884
+ nullable: true,
885
+ transformer: ObjectID.getDatabaseTransformer(),
886
+ })
887
+ public userMicrosoftTeamsId?: ObjectID = undefined;
888
+
754
889
  /*
755
890
  * READ IS OWNER-ONLY ON THE RELATION, EVEN THOUGH THIS TABLE IS ADMIN-READABLE.
756
891
  *
@@ -1020,8 +1155,9 @@ class UserNotificationRule extends BaseModel {
1020
1155
  * An opt-out row is a rule that exists in order to send nothing. It carries a
1021
1156
  * `ruleType` and a severity exactly like a normal rule, sets `isOptOut` to true, and
1022
1157
  * holds NO notification-method relation at all — there is no userEmail, userSms,
1023
- * userCall, userPush, userWhatsApp, userTelegram or userWebhook on it, and that
1024
- * absence is the point rather than a misconfiguration.
1158
+ * userCall, userPush, userWhatsApp, userTelegram, userSlack, userMicrosoftTeams
1159
+ * or userWebhook on it, and that absence is the point rather than a
1160
+ * misconfiguration.
1025
1161
  *
1026
1162
  * It exists because "this user has zero rules matching this ruleType and severity" is
1027
1163
  * otherwise two completely different situations wearing the same clothes. Almost