@oneuptime/common 12.0.18 → 12.0.20

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 (262) 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/EnterpriseLicenseAPI.ts +94 -19
  11. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  12. package/Server/API/UserSlackAPI.ts +125 -0
  13. package/Server/EnvironmentConfig.ts +29 -107
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  19. package/Server/Infrastructure/Queue.ts +7 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +171 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Response.ts +2 -2
  40. package/Server/Utils/StartServer.ts +2 -2
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  42. package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
  43. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  44. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  45. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
  46. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
  47. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
  48. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
  49. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
  50. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  51. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  52. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  53. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  54. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -0
  55. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  56. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  57. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  58. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  59. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  60. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  61. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  62. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  63. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  64. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  65. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  66. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +138 -0
  67. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
  68. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  69. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  70. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  71. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  72. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  73. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  74. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  75. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  76. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  77. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  78. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  79. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  80. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  81. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  82. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  83. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  84. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  85. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  86. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  87. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  88. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  89. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +53 -0
  90. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  91. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  92. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  93. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  94. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  95. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  96. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
  97. package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
  98. package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
  99. package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
  100. package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
  101. package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
  102. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  103. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  104. package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
  105. package/Types/Analytics/RevenueEvent.ts +1 -0
  106. package/Types/Marketing/MarketingEvent.ts +140 -0
  107. package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
  108. package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
  109. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
  110. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
  111. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
  112. package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
  113. package/UI/Components/Charts/Line/LineChart.tsx +8 -1
  114. package/UI/Components/Detail/Detail.tsx +18 -1
  115. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  116. package/UI/Utils/PermissionGate.ts +56 -0
  117. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -0
  118. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  119. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  121. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  123. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  125. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  126. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  127. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  129. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  131. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  133. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  134. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  135. package/build/dist/Server/API/EnterpriseLicenseAPI.js +66 -12
  136. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  137. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  138. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  139. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  140. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  141. package/build/dist/Server/EnvironmentConfig.js +25 -70
  142. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  151. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  152. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  153. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  154. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  155. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  156. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  157. package/build/dist/Server/Services/Index.js +6 -2
  158. package/build/dist/Server/Services/Index.js.map +1 -1
  159. package/build/dist/Server/Services/LlmLogService.js +25 -9
  160. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  161. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  162. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  163. package/build/dist/Server/Services/ProjectService.js +144 -0
  164. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  165. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  166. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  167. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  168. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  169. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  170. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  171. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  172. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  173. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  174. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  175. package/build/dist/Server/Services/UserService.js +26 -1
  176. package/build/dist/Server/Services/UserService.js.map +1 -1
  177. package/build/dist/Server/Services/UserSlackService.js +195 -0
  178. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  179. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  180. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  181. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  182. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  183. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  184. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  185. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  186. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  187. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  188. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  190. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  192. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  193. package/build/dist/Server/Utils/Response.js +2 -2
  194. package/build/dist/Server/Utils/Response.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +2 -2
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  198. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  199. package/build/dist/Types/Analytics/RevenueEvent.js +1 -0
  200. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
  201. package/build/dist/Types/Marketing/MarketingEvent.js +82 -0
  202. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  203. package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
  204. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  205. package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
  206. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  207. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
  208. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  209. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
  210. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  211. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
  212. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  213. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
  214. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
  215. package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
  216. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  217. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  218. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  219. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  220. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  221. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  222. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  223. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
  224. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
  225. package/package.json +1 -1
  226. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  227. package/Server/Services/MarketingConversionService.ts +0 -10
  228. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  229. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  230. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  231. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  232. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  233. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  234. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  235. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  236. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  237. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  238. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  239. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  240. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  241. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  242. package/Types/Marketing/MarketingConversion.ts +0 -53
  243. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  244. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  245. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  246. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  247. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  248. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  249. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  250. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  251. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  252. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  253. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  254. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  255. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  256. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  257. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  258. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  259. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  260. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  261. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  262. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -0,0 +1,321 @@
1
+ import { JSONObject, JSONValue } from "../../../Types/JSON";
2
+ import { isSensitiveLogKey, REDACTED } from "../LogRedaction";
3
+
4
+ /*
5
+ * Credential stripping for monitor ingest payloads.
6
+ *
7
+ * https://github.com/OneUptime/oneuptime/issues/3360
8
+ *
9
+ * The infrastructure agent authenticates by putting the monitor's
10
+ * `serverMonitorSecretKey` in BOTH the ingest URL and the JSON body
11
+ * (`ServerMonitorReport.SecretKey`, `json:"secretKey"`). The URL copy is
12
+ * transient. The body copy is not: the deserialized body becomes
13
+ * `dataToProcess`, and `dataToProcess` is written verbatim to three places
14
+ * that a read-only principal can select from:
15
+ *
16
+ * - `MonitorLog.logBody` (ClickHouse, `Permission.Viewer` readable),
17
+ * - `Monitor.serverMonitorResponse` (Postgres jsonb, `Viewer` readable),
18
+ * - `MonitorProbe.lastMonitoringLog`.
19
+ *
20
+ * So the shared secret that authenticates every agent in the project sat at
21
+ * rest, in plaintext, behind the lowest privilege OneUptime grants. Rotating
22
+ * did not help: the replacement re-entered the payload on the very next beat.
23
+ *
24
+ * Two entry points, because the two sinks want different things:
25
+ *
26
+ * - `stripAgentCredentials` DELETES the key. It runs at the ingest boundary,
27
+ * before `dataToProcess` exists, so the secret never reaches any sink and
28
+ * never enters the process's monitor-evaluation state at all. Deleting
29
+ * rather than masking keeps `Monitor.serverMonitorResponse` faithful to
30
+ * the `ServerMonitorResponse` interface, which declares no `secretKey`.
31
+ *
32
+ * - `redactForPersistence` MASKS the value with `[REDACTED]`. It runs on the
33
+ * copy `MonitorLogUtil` writes to `logBody`. Masking is the better choice
34
+ * there because `logBody` is a diagnostic record: an operator debugging an
35
+ * API monitor wants to see THAT an `Authorization` header was sent without
36
+ * seeing what it was. It is also the net underneath the boundary strip —
37
+ * it applies to every monitor type and every future payload field, so a
38
+ * credential added to some other ingest path does not silently start
39
+ * accumulating in ClickHouse.
40
+ *
41
+ * What counts as a credential is delegated to `isSensitiveLogKey`, the same
42
+ * deny-by-default classifier the logger uses, so there is one list to keep
43
+ * current rather than two. It is structural only (decided by key name): the
44
+ * textual sweeps in `redactLogString` are right for log lines but would mangle
45
+ * a captured HTTP response body, which is legitimate monitor evidence.
46
+ */
47
+
48
+ // Deep enough for any real ingest payload, shallow enough to bound the walk.
49
+ const MAX_DEPTH: number = 12;
50
+
51
+ type WalkFunction = (
52
+ value: JSONValue,
53
+ drop: boolean,
54
+ depth: number,
55
+ ) => JSONValue;
56
+
57
+ /*
58
+ * One walker, two behaviours. `drop` picks whether a sensitive key is removed
59
+ * from its parent object or kept with its value replaced.
60
+ *
61
+ * Always returns a new object/array rather than mutating in place. The ingest
62
+ * caller hands us an object it is about to keep using, and `MonitorLogUtil`
63
+ * hands us a payload that is still being read by the criteria evaluator — so
64
+ * mutating the input would change what the monitor evaluates, not just what
65
+ * gets stored.
66
+ */
67
+ const walk: WalkFunction = (
68
+ value: JSONValue,
69
+ drop: boolean,
70
+ depth: number,
71
+ ): JSONValue => {
72
+ /*
73
+ * Past the depth ceiling we cannot keep vouching for what is nested below,
74
+ * and a payload that deep is not something a monitor legitimately reports.
75
+ * Drop the subtree instead of passing it through unredacted.
76
+ */
77
+ if (depth > MAX_DEPTH) {
78
+ return null;
79
+ }
80
+
81
+ if (value === null || value === undefined) {
82
+ return value;
83
+ }
84
+
85
+ if (Array.isArray(value)) {
86
+ return value.map((item: JSONValue) => {
87
+ return walk(item, drop, depth + 1);
88
+ });
89
+ }
90
+
91
+ /*
92
+ * Dates and ObjectIDs survive the JSON round-trip as strings, but callers
93
+ * may hand us live instances (the ingest boundary runs before
94
+ * `JSON.stringify`). Anything that is not a plain object is a leaf.
95
+ */
96
+ if (
97
+ typeof value !== "object" ||
98
+ Object.getPrototypeOf(value) !== Object.prototype
99
+ ) {
100
+ return value;
101
+ }
102
+
103
+ const source: JSONObject = value as JSONObject;
104
+ const result: JSONObject = {};
105
+
106
+ for (const key of Object.keys(source)) {
107
+ const child: JSONValue = source[key] as JSONValue;
108
+
109
+ if (isSensitiveLogKey(key, child)) {
110
+ if (drop) {
111
+ continue;
112
+ }
113
+
114
+ result[key] = REDACTED;
115
+ continue;
116
+ }
117
+
118
+ result[key] = walk(child, drop, depth + 1);
119
+ }
120
+
121
+ return result;
122
+ };
123
+
124
+ export type StripAgentCredentialsFunction = <T>(payload: T) => T;
125
+
126
+ /*
127
+ * Ingest boundary. Removes agent-supplied credentials from a freshly
128
+ * deserialized payload so the secret never becomes part of `dataToProcess`.
129
+ *
130
+ * Generic over the payload type because the caller immediately treats the
131
+ * result as its declared interface (`ServerMonitorResponse` and friends) —
132
+ * none of which declare a credential field, so removing one cannot invalidate
133
+ * the type.
134
+ */
135
+ export const stripAgentCredentials: StripAgentCredentialsFunction = <T>(
136
+ payload: T,
137
+ ): T => {
138
+ if (payload === null || payload === undefined) {
139
+ return payload;
140
+ }
141
+
142
+ return walk(payload as JSONValue, true, 0) as T;
143
+ };
144
+
145
+ export type RedactForPersistenceFunction = (payload: JSONValue) => JSONValue;
146
+
147
+ /*
148
+ * Storage boundary. Masks credentials in the copy that is about to be written
149
+ * to `MonitorLog.logBody`.
150
+ */
151
+ export const redactForPersistence: RedactForPersistenceFunction = (
152
+ payload: JSONValue,
153
+ ): JSONValue => {
154
+ if (payload === null || payload === undefined) {
155
+ return payload;
156
+ }
157
+
158
+ return walk(payload, false, 0);
159
+ };
160
+
161
+ export type RedactMonitorSecretFunction = <T>(
162
+ payload: T,
163
+ secret: string | null | undefined,
164
+ ) => T;
165
+
166
+ /*
167
+ * Value boundary. Masks every textual occurrence of ONE monitor's own secret
168
+ * key inside an ingest payload.
169
+ *
170
+ * The two walkers above are structural: they decide by key name, which is the
171
+ * right test when a credential arrives as its own field (`secretKey`). The
172
+ * incoming-email path has the other shape. There the secret is not a field at
173
+ * all -- it is baked into a value that is otherwise legitimate evidence.
174
+ * `MonitorService` mints `incomingEmailSecretKey`, and
175
+ * `SendGridInboundProvider.generateMonitorEmailAddress` turns it into the
176
+ * monitor's ingest address, `monitor-{secretKey}@{inboundDomain}`. The address
177
+ * IS the credential -- `extractSecretKeyFromEmail` parses the key straight back
178
+ * out of it -- so everything that quotes the recipient quotes the key:
179
+ * `emailTo`, the `To:` header, and the `Received:` / `Delivered-To:` headers a
180
+ * relay adds on the way in.
181
+ *
182
+ * No key name marks any of those as sensitive, and none of them can simply be
183
+ * dropped. `IncomingEmailCriteria` evaluates `emailTo` for the "Email to"
184
+ * check, and mail that reaches the monitor through an alias or a forwarding
185
+ * rule carries a genuinely different -- and genuinely interesting -- recipient.
186
+ * So this masks the secret substring and leaves the rest of the value intact:
187
+ * domain, alias and relay chain all survive, and a filter written against them
188
+ * keeps matching.
189
+ *
190
+ * Matching is case-insensitive because the key is a UUID that reaches us
191
+ * through address normalisation (`extractEmailAddress` lowercases) and through
192
+ * relay headers that preserve whatever case the sender happened to use.
193
+ */
194
+
195
+ /*
196
+ * A secret shorter than this is not a key we minted -- `ObjectID.generate()`
197
+ * returns a 36-character UUID. Sweeping for a short string would mask ordinary
198
+ * text that merely contains it, destroying the evidence this is trying to keep
199
+ * readable, so treat it as nothing to do rather than over-redact.
200
+ */
201
+ const MIN_SWEEPABLE_SECRET_LENGTH: number = 8;
202
+
203
+ type ReplaceAllInsensitiveFunction = (
204
+ haystack: string,
205
+ needle: string,
206
+ ) => string;
207
+
208
+ /*
209
+ * Case-insensitive replace-all done by scan rather than by `RegExp`, so a
210
+ * secret that happens to contain regex metacharacters needs no escaping and
211
+ * cannot alter the pattern. Compares on lowercased copies but slices from the
212
+ * original, so the surrounding text keeps its own casing.
213
+ */
214
+ const replaceAllInsensitive: ReplaceAllInsensitiveFunction = (
215
+ haystack: string,
216
+ needle: string,
217
+ ): string => {
218
+ const lowerHaystack: string = haystack.toLowerCase();
219
+ const lowerNeedle: string = needle.toLowerCase();
220
+
221
+ let result: string = "";
222
+ let cursor: number = 0;
223
+ let index: number = lowerHaystack.indexOf(lowerNeedle, cursor);
224
+
225
+ while (index !== -1) {
226
+ result += haystack.slice(cursor, index) + REDACTED;
227
+ cursor = index + lowerNeedle.length;
228
+ index = lowerHaystack.indexOf(lowerNeedle, cursor);
229
+ }
230
+
231
+ return result + haystack.slice(cursor);
232
+ };
233
+
234
+ type SweepFunction = (
235
+ value: JSONValue,
236
+ secret: string,
237
+ depth: number,
238
+ ) => JSONValue;
239
+
240
+ const sweep: SweepFunction = (
241
+ value: JSONValue,
242
+ secret: string,
243
+ depth: number,
244
+ ): JSONValue => {
245
+ // Same reasoning as `walk`: below the ceiling we cannot vouch for the value.
246
+ if (depth > MAX_DEPTH) {
247
+ return null;
248
+ }
249
+
250
+ if (value === null || value === undefined) {
251
+ return value;
252
+ }
253
+
254
+ if (typeof value === "string") {
255
+ return replaceAllInsensitive(value, secret);
256
+ }
257
+
258
+ if (Array.isArray(value)) {
259
+ return value.map((item: JSONValue) => {
260
+ return sweep(item, secret, depth + 1);
261
+ });
262
+ }
263
+
264
+ /*
265
+ * Leaf. Dates and ObjectIDs land here as live instances on the ingest side;
266
+ * neither can carry the secret as text, so they pass through untouched.
267
+ */
268
+ if (
269
+ typeof value !== "object" ||
270
+ Object.getPrototypeOf(value) !== Object.prototype
271
+ ) {
272
+ return value;
273
+ }
274
+
275
+ const source: JSONObject = value as JSONObject;
276
+ const result: JSONObject = {};
277
+
278
+ /*
279
+ * Keys are swept too. `emailHeaders` is a `Dictionary<string>` built from
280
+ * whatever header names the relay sent, so a key is no more trustworthy than
281
+ * a value -- and a redacted key still serialises into the stored jsonb.
282
+ */
283
+ for (const key of Object.keys(source)) {
284
+ result[replaceAllInsensitive(key, secret)] = sweep(
285
+ source[key] as JSONValue,
286
+ secret,
287
+ depth + 1,
288
+ );
289
+ }
290
+
291
+ return result;
292
+ };
293
+
294
+ /*
295
+ * Generic over the payload type for the same reason as `stripAgentCredentials`:
296
+ * the caller goes on treating the result as its declared interface. Masking
297
+ * only ever rewrites strings in place, so every field keeps its declared type.
298
+ */
299
+ export const redactMonitorSecret: RedactMonitorSecretFunction = <T>(
300
+ payload: T,
301
+ secret: string | null | undefined,
302
+ ): T => {
303
+ if (payload === null || payload === undefined) {
304
+ return payload;
305
+ }
306
+
307
+ if (
308
+ typeof secret !== "string" ||
309
+ secret.length < MIN_SWEEPABLE_SECRET_LENGTH
310
+ ) {
311
+ return payload;
312
+ }
313
+
314
+ return sweep(payload as JSONValue, secret, 0) as T;
315
+ };
316
+
317
+ export default {
318
+ stripAgentCredentials,
319
+ redactForPersistence,
320
+ redactMonitorSecret,
321
+ };
@@ -25,7 +25,7 @@ import MimeType from "../../Types/File/MimeType";
25
25
  import PositiveNumber from "../../Types/PositiveNumber";
26
26
  import Route from "../../Types/API/Route";
27
27
  import CaptureSpan from "./Telemetry/CaptureSpan";
28
- import { IsBillingEnabled } from "../EnvironmentConfig";
28
+ import { GoogleTagManagerEnabled } from "../EnvironmentConfig";
29
29
 
30
30
  /*
31
31
  * Raster image types, which is to say the types that cannot carry script and
@@ -239,7 +239,7 @@ export default class Response {
239
239
  * already use; passing the flag explicitly still wins.
240
240
  */
241
241
  oneUptimeResponse.render(path, {
242
- enableGoogleTagManager: IsBillingEnabled,
242
+ enableGoogleTagManager: GoogleTagManagerEnabled,
243
243
  ...vars,
244
244
  });
245
245
  }
@@ -3,7 +3,7 @@ import CommonAPI from "../API/Index";
3
3
  import { StatusAPIOptions } from "../API/StatusAPI";
4
4
  import {
5
5
  AppVersion,
6
- IsBillingEnabled,
6
+ GoogleTagManagerEnabled,
7
7
  TrustedProxyHops,
8
8
  getFrontendEnvVars,
9
9
  } from "../EnvironmentConfig";
@@ -449,7 +449,7 @@ const init: InitFunction = async (
449
449
  }
450
450
 
451
451
  return res.render(path.resolve(process.cwd(), "views/index.ejs"), {
452
- enableGoogleTagManager: IsBillingEnabled || false,
452
+ enableGoogleTagManager: GoogleTagManagerEnabled,
453
453
  ...variables,
454
454
  });
455
455
  } catch (err) {
@@ -76,6 +76,7 @@ import {
76
76
  TeamsChannelAccount,
77
77
  TeamsPagedMembersResult,
78
78
  TurnContext,
79
+ ConversationParameters,
79
80
  ConversationReference,
80
81
  MessageFactory,
81
82
  ConfigurationBotFrameworkAuthenticationOptions,
@@ -902,6 +903,163 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
902
903
  });
903
904
  }
904
905
 
906
+ /*
907
+ * Sends a 1:1 message to a Teams user as the OneUptime bot, addressed by the
908
+ * user's Microsoft Entra object id (which is what WorkspaceUserAuthToken
909
+ * stores as workspaceUserId).
910
+ *
911
+ * This cannot go through sendDirectMessageToUser above: that method posts to
912
+ * Graph's /chats/{chatId}/messages, which needs an existing chat id and a
913
+ * delegated token. A personal chat between the bot and the user may not
914
+ * exist yet, so this uses the Bot Framework's createConversation, which
915
+ * resolves (or creates) the 1:1 conversation from the member's Entra id -
916
+ * provided the OneUptime app is installed for that user. When the personal
917
+ * chat was already captured from a bot activity, it is reused instead, which
918
+ * also preserves the regional serviceUrl captured with it.
919
+ */
920
+ @CaptureSpan()
921
+ public static async sendDirectMessageToUserAsBot(data: {
922
+ projectId: ObjectID;
923
+ workspaceUserId: string;
924
+ messageBlocks: Array<WorkspaceMessageBlock>;
925
+ }): Promise<void> {
926
+ const adaptiveCard: JSONObject = this.buildAdaptiveCardFromMessageBlocks({
927
+ messageBlocks: data.messageBlocks,
928
+ });
929
+
930
+ const projectAuth: WorkspaceProjectAuthToken | null =
931
+ await WorkspaceProjectAuthTokenService.getProjectAuth({
932
+ projectId: data.projectId,
933
+ workspaceType: WorkspaceType.MicrosoftTeams,
934
+ });
935
+
936
+ if (!projectAuth || !projectAuth.miscData) {
937
+ throw new BadDataException(
938
+ "Microsoft Teams integration not found for this project",
939
+ );
940
+ }
941
+
942
+ const miscData: MicrosoftTeamsMiscData =
943
+ projectAuth.miscData as MicrosoftTeamsMiscData;
944
+
945
+ const tenantId: string | undefined = projectAuth.workspaceProjectId;
946
+
947
+ if (!tenantId) {
948
+ throw new BadDataException(
949
+ "Tenant ID not found in Microsoft Teams integration",
950
+ );
951
+ }
952
+
953
+ if (!MicrosoftTeamsAppClientId) {
954
+ throw new BadDataException(
955
+ "Microsoft Teams App Client ID not configured",
956
+ );
957
+ }
958
+
959
+ /*
960
+ * Prefer a personal chat that was already captured from a bot activity:
961
+ * it is known-deliverable and carries the regional serviceUrl.
962
+ */
963
+ const capturedPersonalChat: MicrosoftTeamsChat | undefined = Object.values(
964
+ miscData.availableChats || {},
965
+ ).find((chat: MicrosoftTeamsChat) => {
966
+ return (
967
+ chat.chatType === "personal" &&
968
+ (chat.memberAadObjectIds || []).includes(data.workspaceUserId)
969
+ );
970
+ });
971
+
972
+ if (capturedPersonalChat) {
973
+ await this.sendAdaptiveCardToChat({
974
+ chatId: capturedPersonalChat.id,
975
+ projectId: data.projectId,
976
+ adaptiveCard: adaptiveCard,
977
+ });
978
+ return;
979
+ }
980
+
981
+ /*
982
+ * No captured personal chat - create (or resolve) the 1:1 conversation
983
+ * proactively. A regional serviceUrl captured from any prior activity is
984
+ * preferred; the commercial-cloud global endpoint is the fallback.
985
+ */
986
+ const serviceUrl: string =
987
+ Object.values(miscData.availableChats || {}).find(
988
+ (chat: MicrosoftTeamsChat) => {
989
+ return Boolean(chat.serviceUrl);
990
+ },
991
+ )?.serviceUrl ||
992
+ Object.values(miscData.installedTeams || {}).find(
993
+ (team: MicrosoftTeamsInstalledTeam) => {
994
+ return Boolean(team.serviceUrl);
995
+ },
996
+ )?.serviceUrl ||
997
+ "https://smba.trafficmanager.net/teams/";
998
+
999
+ const adapter: CloudAdapter = this.getBotAdapter();
1000
+
1001
+ const conversationParameters: ConversationParameters = {
1002
+ isGroup: false,
1003
+ // 28:<appId> is the Teams-side bot account id.
1004
+ bot: {
1005
+ id: `28:${MicrosoftTeamsAppClientId}`,
1006
+ name: "OneUptime Bot",
1007
+ },
1008
+ // Teams accepts the member's Microsoft Entra object id here.
1009
+ members: [
1010
+ {
1011
+ id: data.workspaceUserId,
1012
+ name: "",
1013
+ },
1014
+ ],
1015
+ tenantId: tenantId,
1016
+ channelData: {
1017
+ tenant: {
1018
+ id: tenantId,
1019
+ },
1020
+ },
1021
+ };
1022
+
1023
+ try {
1024
+ await adapter.createConversationAsync(
1025
+ MicrosoftTeamsAppClientId,
1026
+ "msteams",
1027
+ serviceUrl,
1028
+ undefined as unknown as string,
1029
+ conversationParameters,
1030
+ async (context: TurnContext) => {
1031
+ const message: Partial<Activity> = MessageFactory.attachment({
1032
+ contentType: "application/vnd.microsoft.card.adaptive",
1033
+ content: adaptiveCard,
1034
+ });
1035
+
1036
+ await context.sendActivity(message);
1037
+ },
1038
+ );
1039
+ } catch (error) {
1040
+ logger.error(
1041
+ "Error sending Microsoft Teams direct message via Bot Framework:",
1042
+ {
1043
+ projectId: data.projectId.toString(),
1044
+ },
1045
+ );
1046
+ logger.error(error);
1047
+
1048
+ /*
1049
+ * Microsoft's roster rejection is meaningless to the person being paged.
1050
+ * For a 1:1 conversation it means the OneUptime app is not installed for
1051
+ * this user, so say exactly that.
1052
+ */
1053
+ if (this.isBotNotInConversationRosterError(error)) {
1054
+ throw new BadDataException(
1055
+ "The OneUptime app is not installed for this Microsoft Teams user. Ask them to add the OneUptime app in Microsoft Teams (personal scope) so the bot can message them directly.",
1056
+ );
1057
+ }
1058
+
1059
+ throw error;
1060
+ }
1061
+ }
1062
+
905
1063
  @CaptureSpan()
906
1064
  public static override async createChannelsIfDoesNotExist(data: {
907
1065
  authToken: string;
@@ -4154,6 +4312,18 @@ All monitoring checks are passing normally.`;
4154
4312
  if (data.serviceUrl && chat.serviceUrl !== data.serviceUrl) {
4155
4313
  return false; // stored serviceUrl is stale.
4156
4314
  }
4315
+
4316
+ /*
4317
+ * Personal chats captured before member Entra ids were recorded cannot
4318
+ * serve as direct-message targets — treat them as stale so the next
4319
+ * activity on the chat backfills the roster.
4320
+ */
4321
+ if (
4322
+ chat.chatType === "personal" &&
4323
+ (chat.memberAadObjectIds || []).length === 0
4324
+ ) {
4325
+ return false;
4326
+ }
4157
4327
  }
4158
4328
 
4159
4329
  return true;
@@ -4222,6 +4392,7 @@ All monitoring checks are passing normally.`;
4222
4392
  | undefined;
4223
4393
 
4224
4394
  let memberNames: Array<string> = [];
4395
+ let memberAadObjectIds: Array<string> = [];
4225
4396
 
4226
4397
  try {
4227
4398
  const botId: string | undefined =
@@ -4243,12 +4414,27 @@ All monitoring checks are passing normally.`;
4243
4414
  continuationToken = page.continuationToken;
4244
4415
  } while (continuationToken);
4245
4416
 
4246
- memberNames = members
4247
- .filter((member: TeamsChannelAccount) => {
4417
+ const humanMembers: Array<TeamsChannelAccount> = members.filter(
4418
+ (member: TeamsChannelAccount) => {
4248
4419
  return member.id !== botId;
4249
- })
4420
+ },
4421
+ );
4422
+
4423
+ memberNames = humanMembers.map((member: TeamsChannelAccount) => {
4424
+ return member.name || "";
4425
+ });
4426
+
4427
+ /*
4428
+ * The Entra object id is what maps a chat member back to the
4429
+ * OneUptime user who connected that Teams account, so personal chats
4430
+ * can serve as direct-message delivery targets.
4431
+ */
4432
+ memberAadObjectIds = humanMembers
4250
4433
  .map((member: TeamsChannelAccount) => {
4251
- return member.name || "";
4434
+ return member.aadObjectId || "";
4435
+ })
4436
+ .filter((aadObjectId: string) => {
4437
+ return Boolean(aadObjectId);
4252
4438
  });
4253
4439
  } catch (err) {
4254
4440
  logger.debug("Could not fetch chat members for chat name resolution");
@@ -4265,6 +4451,7 @@ All monitoring checks are passing normally.`;
4265
4451
  chatType: chatType,
4266
4452
  serviceUrl: activityServiceUrl,
4267
4453
  addedAt: OneUptimeDate.getCurrentDate().toISOString(),
4454
+ memberAadObjectIds: memberAadObjectIds,
4268
4455
  };
4269
4456
 
4270
4457
  await this.saveChatToProjectAuthTokens({