@oneuptime/common 12.0.12 → 12.0.13

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 (234) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  27. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  28. package/Server/Utils/LogRedaction.ts +576 -0
  29. package/Server/Utils/Logger.ts +123 -46
  30. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  31. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  32. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  33. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  34. package/Server/Utils/StartServer.ts +30 -9
  35. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  36. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  37. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  38. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  39. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  40. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  41. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  42. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  43. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  44. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  45. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  46. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  47. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  48. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  49. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  50. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  51. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  52. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  53. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  54. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  55. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  56. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  57. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  58. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  59. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  60. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  61. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  62. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  63. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  65. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  66. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  67. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  68. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  69. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  70. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  71. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  72. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  73. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  74. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  75. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  76. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  77. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  78. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  79. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  80. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  81. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  82. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  83. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  84. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  85. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  86. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  87. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  88. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  89. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  90. package/Types/API/Hostname.ts +79 -0
  91. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  92. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  93. package/Types/Database/DatabaseProperty.ts +38 -0
  94. package/Types/Database/TableColumn.ts +34 -7
  95. package/Types/Events/Recurring.ts +16 -1
  96. package/Types/Monitor/MonitorStep.ts +13 -1
  97. package/Types/Monitor/MonitorType.ts +345 -40
  98. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  99. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  100. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  101. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  102. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  103. package/UI/Components/Footer/Footer.tsx +32 -6
  104. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  105. package/UI/Components/Forms/Types/Field.ts +8 -0
  106. package/UI/Components/Loader/PageLoader.tsx +29 -8
  107. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  108. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  109. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  110. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  111. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  112. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  113. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  114. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  115. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  116. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  118. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  120. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  122. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  123. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  124. package/build/dist/Server/API/AIChatAPI.js +48 -35
  125. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  126. package/build/dist/Server/API/CommonAPI.js +59 -0
  127. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  128. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  129. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  130. package/build/dist/Server/API/SlackAPI.js +13 -10
  131. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  132. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  133. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  134. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  135. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  136. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  137. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  138. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  139. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  140. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  142. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  143. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  144. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  145. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  146. package/build/dist/Server/Services/DatabaseService.js +15 -6
  147. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  148. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  149. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  150. package/build/dist/Server/Services/LogService.js +16 -0
  151. package/build/dist/Server/Services/LogService.js.map +1 -1
  152. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  153. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  154. package/build/dist/Server/Services/SpanService.js +16 -0
  155. package/build/dist/Server/Services/SpanService.js.map +1 -1
  156. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  157. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  158. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  159. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  160. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  161. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  162. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  163. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  164. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  165. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  166. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  167. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  168. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  169. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  170. package/build/dist/Server/Utils/Logger.js +98 -45
  171. package/build/dist/Server/Utils/Logger.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  173. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  174. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  175. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  176. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  177. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  178. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  179. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  180. package/build/dist/Server/Utils/StartServer.js +10 -7
  181. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  182. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  183. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  184. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  185. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  186. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  187. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  188. package/build/dist/Types/API/Hostname.js +63 -0
  189. package/build/dist/Types/API/Hostname.js.map +1 -1
  190. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  191. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  192. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  193. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  194. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  195. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  196. package/build/dist/Types/Database/TableColumn.js +24 -6
  197. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  198. package/build/dist/Types/Events/Recurring.js +13 -1
  199. package/build/dist/Types/Events/Recurring.js.map +1 -1
  200. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  201. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  202. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  203. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  204. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  205. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  206. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  207. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  208. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  209. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  210. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  211. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  212. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  213. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  214. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  215. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  216. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  217. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  218. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  219. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  220. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  221. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  222. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  223. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  224. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  225. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  226. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  227. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  228. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  229. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  230. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  231. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  232. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  233. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  234. package/package.json +1 -1
@@ -44,6 +44,20 @@ import MicrosoftTeamsOnCallDutyActions from "./Actions/OnCallDutyPolicy";
44
44
  import AccessTokenService from "../../../Services/AccessTokenService";
45
45
  // Microsoft Teams apps should always be single-tenant
46
46
  const MICROSOFT_TEAMS_APP_TYPE = "SingleTenant";
47
+ /*
48
+ * Whether the OneUptime Teams app is installed in a given team.
49
+ *
50
+ * Unknown is a first-class outcome, not an error: checking installation needs a
51
+ * Graph permission that deployments connected before it was documented have not
52
+ * consented to, and a tenant that answers "I won't tell you" must never be
53
+ * reported to an admin as "the app is not installed".
54
+ */
55
+ export var MicrosoftTeamsAppInstallState;
56
+ (function (MicrosoftTeamsAppInstallState) {
57
+ MicrosoftTeamsAppInstallState["Installed"] = "Installed";
58
+ MicrosoftTeamsAppInstallState["NotInstalled"] = "NotInstalled";
59
+ MicrosoftTeamsAppInstallState["Unknown"] = "Unknown";
60
+ })(MicrosoftTeamsAppInstallState || (MicrosoftTeamsAppInstallState = {}));
47
61
  /*
48
62
  * Microsoft's wording when the Bot Framework refuses a proactive post because
49
63
  * the app is not a member of the target conversation. Matched case-insensitively
@@ -959,6 +973,12 @@ class MicrosoftTeamsUtil extends WorkspaceBase {
959
973
  });
960
974
  logger.debug(`Team ID: ${data.teamId}`);
961
975
  logger.debug(`Adaptive card: ${JSON.stringify(data.adaptiveCard)}`);
976
+ /*
977
+ * Declared out here so the catch block can tell a rejection we predicted
978
+ * from one we could not, and word the error accordingly.
979
+ */
980
+ let installState = MicrosoftTeamsAppInstallState.Unknown;
981
+ let installStateCameFromGraph = false;
962
982
  try {
963
983
  // Get project auth to retrieve bot ID
964
984
  const projectAuth = await WorkspaceProjectAuthTokenService.getProjectAuth({
@@ -989,25 +1009,36 @@ class MicrosoftTeamsUtil extends WorkspaceBase {
989
1009
  throw new BadDataException(`"${data.workspaceChannel.name}" is a shared channel, and Microsoft Teams does not allow bots to post in shared channels. Please pick a standard or private channel instead.`);
990
1010
  }
991
1011
  /*
992
- * Preflight: refuse before calling the Bot Framework when we know the
993
- * app was never installed into this team. Channels are discovered with
994
- * tenant-wide Graph application permissions, which see every team
995
- * regardless of installation — so reaching this point proves nothing
996
- * about whether we can actually post.
1012
+ * Preflight. Channels are discovered with tenant-wide Graph application
1013
+ * permissions, which see every team regardless of installation so
1014
+ * reaching this point proves nothing about whether we can actually post.
997
1015
  *
998
- * installedTeams is only populated from install events, so an empty map
999
- * means "we have not observed any installs" (for example on a workspace
1000
- * connected before this shipped), not "nothing is installed". Only
1001
- * enforce once we have at least one record, and let the Bot Framework
1002
- * error be translated below otherwise.
1016
+ * An install we recorded from a bot activity is the cheap positive
1017
+ * signal, and it also carries the serviceUrl this send needs. When we
1018
+ * have no record we ask Graph rather than assuming: absence from
1019
+ * installedTeams used to be reported to admins as "the app is not
1020
+ * installed", which is a claim install events alone cannot support (they
1021
+ * only arrive for installs that happened while OneUptime was reachable).
1022
+ * Only a verified negative refuses the send; anything we cannot verify
1023
+ * goes to Microsoft, which is the real authority.
1003
1024
  */
1004
- const installedTeams = miscData.installedTeams || {};
1005
- const installedTeam = installedTeams[data.teamId];
1006
- if (Object.keys(installedTeams).length > 0 && !installedTeam) {
1007
- throw new BadDataException(this.getBotNotInTeamMessage({
1008
- channelName: data.workspaceChannel.name,
1009
- membershipType: data.workspaceChannel.membershipType,
1010
- }));
1025
+ const installedTeam = this.indexInstalledTeamsByGraphTeamId(miscData.installedTeams)[data.teamId];
1026
+ if (installedTeam) {
1027
+ installState = MicrosoftTeamsAppInstallState.Installed;
1028
+ }
1029
+ else {
1030
+ installState = await this.isAppInstalledInTeam({
1031
+ authToken: data.authToken,
1032
+ projectId: data.projectId,
1033
+ teamId: data.teamId,
1034
+ });
1035
+ installStateCameFromGraph = true;
1036
+ if (installState === MicrosoftTeamsAppInstallState.NotInstalled) {
1037
+ throw new BadDataException(this.getBotNotInTeamMessage({
1038
+ channelName: data.workspaceChannel.name,
1039
+ membershipType: data.workspaceChannel.membershipType,
1040
+ }));
1041
+ }
1011
1042
  }
1012
1043
  // Get Bot Framework adapter
1013
1044
  const adapter = this.getBotAdapter();
@@ -1062,13 +1093,39 @@ class MicrosoftTeamsUtil extends WorkspaceBase {
1062
1093
  logger.error(error);
1063
1094
  /*
1064
1095
  * Microsoft's roster rejection is meaningless to an admin ("The bot is
1065
- * not part of the conversation roster"). Replace it with the action that
1066
- * actually fixes it.
1096
+ * not part of the conversation roster"). Replace it with something
1097
+ * actionable but only claim the app is missing from the team when we
1098
+ * checked and it is. Otherwise the app can be installed exactly as
1099
+ * documented and still be told to install it, which is how this error
1100
+ * sent admins in circles.
1067
1101
  */
1068
1102
  if (this.isBotNotInConversationRosterError(error)) {
1069
- throw new BadDataException(this.getBotNotInTeamMessage({
1103
+ /*
1104
+ * A local install record got us past the preflight without asking
1105
+ * Graph, and it can be stale — the app may have been removed from the
1106
+ * team since, and an uninstall event only reaches us if OneUptime was
1107
+ * reachable at the time. So confirm before wording the error. This is
1108
+ * the failure path, so the extra call costs nothing in normal operation.
1109
+ */
1110
+ let verifiedInstallState = installState;
1111
+ if (!installStateCameFromGraph) {
1112
+ verifiedInstallState = await this.isAppInstalledInTeam({
1113
+ authToken: data.authToken,
1114
+ projectId: data.projectId,
1115
+ teamId: data.teamId,
1116
+ });
1117
+ }
1118
+ if (verifiedInstallState === MicrosoftTeamsAppInstallState.NotInstalled) {
1119
+ throw new BadDataException(this.getBotNotInTeamMessage({
1120
+ channelName: data.workspaceChannel.name,
1121
+ membershipType: data.workspaceChannel.membershipType,
1122
+ }));
1123
+ }
1124
+ throw new BadDataException(this.getRosterRejectionMessage({
1070
1125
  channelName: data.workspaceChannel.name,
1071
1126
  membershipType: data.workspaceChannel.membershipType,
1127
+ installState: verifiedInstallState,
1128
+ microsoftError: error,
1072
1129
  }));
1073
1130
  }
1074
1131
  throw error;
@@ -1098,6 +1155,94 @@ class MicrosoftTeamsUtil extends WorkspaceBase {
1098
1155
  }
1099
1156
  return `The OneUptime app is not installed in the Microsoft Teams team that owns "${data.channelName}". In Microsoft Teams, click the "..." next to the team name, then Manage team > Apps > More apps, and add OneUptime. Installing OneUptime for yourself or in a chat is not the same as adding it to the team.`;
1100
1157
  }
1158
+ /*
1159
+ * Microsoft rejected the post and we could not confirm why. Everything here
1160
+ * is a real cause of that rejection for an app that IS present in the team's
1161
+ * app list, which is the case getBotNotInTeamMessage got wrong: the app tile
1162
+ * showing up in Manage team > Apps does not mean the bot behind it is the one
1163
+ * this deployment authenticates as. Microsoft's own wording is included so
1164
+ * the raw error is not lost in the rewrite.
1165
+ */
1166
+ static getRosterRejectionMessage(data) {
1167
+ const causes = [];
1168
+ if (data.membershipType === "private") {
1169
+ causes.push(`"${data.channelName}" is a private channel, which needs the app installed into the channel itself (channel "..." > Manage channel > Apps > Add an app) — a team-level install does not cover it`);
1170
+ }
1171
+ else if (data.installState === MicrosoftTeamsAppInstallState.Installed) {
1172
+ causes.push("the app is installed in this team, so the bot behind it may not be the one this OneUptime deployment uses — check that the Teams app package was built from this deployment (Project Settings > Workspace > Microsoft Teams) and that its bot id matches MICROSOFT_TEAMS_APP_CLIENT_ID");
1173
+ }
1174
+ else {
1175
+ causes.push('the OneUptime app has not been added to this team (team "..." > Manage team > Apps > More apps), or the app that was added is a different package whose bot id is not this deployment\'s MICROSOFT_TEAMS_APP_CLIENT_ID');
1176
+ }
1177
+ causes.push("the Azure Bot resource for this deployment does not have the Microsoft Teams channel enabled");
1178
+ const microsoftMessage = data.microsoftError instanceof Error
1179
+ ? data.microsoftError.message
1180
+ : String(data.microsoftError || "");
1181
+ const suffix = microsoftMessage
1182
+ ? ` Microsoft's response was: "${microsoftMessage}"`
1183
+ : "";
1184
+ return `Microsoft Teams refused the message to "${data.channelName}" because the OneUptime bot is not a member of that conversation. Likely causes: ${causes.join("; ")}.${suffix}`;
1185
+ }
1186
+ /*
1187
+ * Asks Graph whether this deployment's Teams app is installed in a team.
1188
+ *
1189
+ * Matching is on teamsApp.externalId, which is the manifest id — and the
1190
+ * manifest OneUptime generates sets both that and the bot id to
1191
+ * MICROSOFT_TEAMS_APP_CLIENT_ID. So this answers the question that actually
1192
+ * matters ("is the app THIS deployment authenticates as installed here?"),
1193
+ * not the weaker one an admin can answer by eye ("is something called
1194
+ * OneUptime in the app list?").
1195
+ *
1196
+ * Every failure is Unknown. The call needs TeamsAppInstallation.ReadForTeam.All,
1197
+ * which deployments connected before it was documented have not granted, and a
1198
+ * missing permission must not be reported as a missing install.
1199
+ */
1200
+ static async isAppInstalledInTeam(data) {
1201
+ if (!MicrosoftTeamsAppClientId) {
1202
+ return MicrosoftTeamsAppInstallState.Unknown;
1203
+ }
1204
+ try {
1205
+ const accessToken = await this.getValidAccessToken({
1206
+ authToken: data.authToken,
1207
+ projectId: data.projectId,
1208
+ });
1209
+ let nextLink = `https://graph.microsoft.com/v1.0/teams/${data.teamId}/installedApps?$expand=teamsApp`;
1210
+ let pageCount = 0;
1211
+ while (nextLink) {
1212
+ pageCount++;
1213
+ if (pageCount > MICROSOFT_TEAMS_MAX_PAGES) {
1214
+ logger.warn(`Stopped paginating installed apps for team ${data.teamId} after ${MICROSOFT_TEAMS_MAX_PAGES} pages.`);
1215
+ return MicrosoftTeamsAppInstallState.Unknown;
1216
+ }
1217
+ const response = await API.get({
1218
+ url: URL.fromString(nextLink),
1219
+ headers: {
1220
+ Authorization: `Bearer ${accessToken}`,
1221
+ "Content-Type": "application/json",
1222
+ },
1223
+ });
1224
+ if (response instanceof HTTPErrorResponse) {
1225
+ logger.debug(`Could not read installed apps for team ${data.teamId}; treating installation as unknown.`);
1226
+ logger.debug(response);
1227
+ return MicrosoftTeamsAppInstallState.Unknown;
1228
+ }
1229
+ const installedApps = response.data["value"] || [];
1230
+ for (const installedApp of installedApps) {
1231
+ const teamsApp = installedApp["teamsApp"] || {};
1232
+ if (teamsApp["externalId"] === MicrosoftTeamsAppClientId) {
1233
+ return MicrosoftTeamsAppInstallState.Installed;
1234
+ }
1235
+ }
1236
+ nextLink = response.data["@odata.nextLink"] || null;
1237
+ }
1238
+ return MicrosoftTeamsAppInstallState.NotInstalled;
1239
+ }
1240
+ catch (err) {
1241
+ logger.debug(`Error checking whether the OneUptime app is installed in team ${data.teamId}; treating installation as unknown.`);
1242
+ logger.debug(err);
1243
+ return MicrosoftTeamsAppInstallState.Unknown;
1244
+ }
1245
+ }
1101
1246
  /*
1102
1247
  * Sends an adaptive card to a Teams group chat or personal (1:1) chat via a
1103
1248
  * proactive Bot Framework message. The OneUptime app must have been added to
@@ -1566,6 +1711,7 @@ class MicrosoftTeamsUtil extends WorkspaceBase {
1566
1711
  await this.captureTeamFromBotActivity({
1567
1712
  activity: data.activity,
1568
1713
  turnContext: data.turnContext,
1714
+ onlyIfMissingOrStale: true,
1569
1715
  });
1570
1716
  }
1571
1717
  // If this is actually an Adaptive Card submit wrapped as a message, route to invoke handler
@@ -2965,13 +3111,93 @@ All monitoring checks are passing normally.`;
2965
3111
  * (channels are not chats), so before this existed we threw away the only
2966
3112
  * signal that tells us a proactive channel post will be accepted.
2967
3113
  */
3114
+ /*
3115
+ * The AAD group id for a team, which is what Graph calls the team id and what
3116
+ * every notification rule stores. Teams normally puts it on the activity as
3117
+ * channelData.team.aadGroupId; when it does not, TeamsInfo.getTeamDetails
3118
+ * fetches it. Returns undefined rather than throwing — a missing group id
3119
+ * degrades the record, it does not invalidate the install.
3120
+ */
3121
+ static async resolveGraphTeamIdFromBotActivity(data) {
3122
+ var _a;
3123
+ const aadGroupIdOnActivity = ((_a = data.team) === null || _a === void 0 ? void 0 : _a["aadGroupId"]) || "";
3124
+ if (aadGroupIdOnActivity) {
3125
+ return aadGroupIdOnActivity;
3126
+ }
3127
+ try {
3128
+ const teamDetails = await TeamsInfo.getTeamDetails(data.turnContext);
3129
+ return (teamDetails === null || teamDetails === void 0 ? void 0 : teamDetails.aadGroupId) || undefined;
3130
+ }
3131
+ catch (err) {
3132
+ /*
3133
+ * Uninstall activities are the common case here: the bot is already out
3134
+ * of the team by the time we ask, so the call fails. Uninstall matches on
3135
+ * the thread id anyway, so this is not worth an error-level log.
3136
+ */
3137
+ logger.debug("Could not resolve the Graph team id for a Microsoft Teams install:");
3138
+ logger.debug(err);
3139
+ return undefined;
3140
+ }
3141
+ }
3142
+ /*
3143
+ * installedTeams re-keyed by Graph team id, which is the id every caller
3144
+ * outside the bot handlers actually holds. Records that could not be resolved
3145
+ * to a group id are dropped: they cannot answer "is this team installed?" for
3146
+ * a notification rule, and including them under their thread id would only
3147
+ * invite the same confusion this replaced.
3148
+ */
3149
+ static indexInstalledTeamsByGraphTeamId(installedTeams) {
3150
+ const index = {};
3151
+ for (const installedTeam of Object.values(installedTeams || {})) {
3152
+ const graphTeamId = (installedTeam === null || installedTeam === void 0 ? void 0 : installedTeam.graphTeamId) || undefined;
3153
+ if (!graphTeamId) {
3154
+ continue;
3155
+ }
3156
+ index[graphTeamId] = installedTeam;
3157
+ }
3158
+ return index;
3159
+ }
3160
+ /*
3161
+ * The stored record for a team in a tenant, if any, matched on either id.
3162
+ * Used to keep the message-driven backfill cheap.
3163
+ */
3164
+ static async getInstalledTeamForTenant(data) {
3165
+ const projectAuths = await WorkspaceProjectAuthTokenService.findBy({
3166
+ query: {
3167
+ workspaceType: WorkspaceType.MicrosoftTeams,
3168
+ workspaceProjectId: data.tenantId,
3169
+ },
3170
+ select: {
3171
+ _id: true,
3172
+ miscData: true,
3173
+ },
3174
+ limit: LIMIT_MAX,
3175
+ skip: 0,
3176
+ props: {
3177
+ isRoot: true,
3178
+ },
3179
+ });
3180
+ for (const projectAuth of projectAuths) {
3181
+ const miscData = projectAuth.miscData || {};
3182
+ for (const existingTeam of Object.values(miscData.installedTeams || {})) {
3183
+ if (this.isSameInstalledTeam({
3184
+ team: existingTeam,
3185
+ graphTeamId: data.graphTeamId,
3186
+ teamsThreadId: data.teamsThreadId,
3187
+ })) {
3188
+ return existingTeam;
3189
+ }
3190
+ }
3191
+ }
3192
+ return null;
3193
+ }
2968
3194
  static async captureTeamFromBotActivity(data) {
2969
3195
  var _a;
2970
3196
  try {
2971
3197
  const channelData = data.activity["channelData"] || {};
2972
3198
  const team = channelData["team"];
2973
- const teamId = (team === null || team === void 0 ? void 0 : team["id"]) || "";
2974
- if (!teamId) {
3199
+ const teamsThreadId = (team === null || team === void 0 ? void 0 : team["id"]) || "";
3200
+ if (!teamsThreadId) {
2975
3201
  // Not a team-scoped activity (personal or group chat). Nothing to do.
2976
3202
  return;
2977
3203
  }
@@ -2980,19 +3206,53 @@ All monitoring checks are passing normally.`;
2980
3206
  logger.debug("No tenant id found on team activity. Skipping.");
2981
3207
  return;
2982
3208
  }
3209
+ const activityServiceUrl = data.activity["serviceUrl"] ||
3210
+ data.turnContext.activity.serviceUrl ||
3211
+ undefined;
3212
+ /*
3213
+ * The backfill runs on every inbound channel message, so it must be cheap
3214
+ * once the team is already on record: one read, then nothing. Without this
3215
+ * every message meant a write, and now potentially a Bot Framework call to
3216
+ * resolve the group id as well. A record that is missing the group id, or
3217
+ * whose serviceUrl has moved, is still worth redoing.
3218
+ */
3219
+ if (data.onlyIfMissingOrStale) {
3220
+ const existingTeam = await this.getInstalledTeamForTenant({
3221
+ tenantId: tenantId,
3222
+ teamsThreadId: teamsThreadId,
3223
+ graphTeamId: (team === null || team === void 0 ? void 0 : team["aadGroupId"]) || undefined,
3224
+ });
3225
+ if (existingTeam &&
3226
+ existingTeam.graphTeamId &&
3227
+ existingTeam.serviceUrl === activityServiceUrl) {
3228
+ return;
3229
+ }
3230
+ }
3231
+ /*
3232
+ * The send path looks installs up by Graph team id, so resolving it here
3233
+ * is what makes the record usable at all. Teams puts it on the activity
3234
+ * as aadGroupId most of the time; when it does not, one Bot Framework
3235
+ * call gets it. A record without it is still worth keeping for the
3236
+ * serviceUrl and for uninstall matching, it just cannot be matched to a
3237
+ * notification rule.
3238
+ */
3239
+ const graphTeamId = await this.resolveGraphTeamIdFromBotActivity({
3240
+ team: team,
3241
+ turnContext: data.turnContext,
3242
+ });
2983
3243
  const installedTeam = {
2984
- id: teamId,
3244
+ id: graphTeamId || teamsThreadId,
3245
+ graphTeamId: graphTeamId,
3246
+ teamsThreadId: teamsThreadId,
2985
3247
  name: (team === null || team === void 0 ? void 0 : team["name"]) || undefined,
2986
- serviceUrl: data.activity["serviceUrl"] ||
2987
- data.turnContext.activity.serviceUrl ||
2988
- undefined,
3248
+ serviceUrl: activityServiceUrl,
2989
3249
  addedAt: OneUptimeDate.getCurrentDate().toISOString(),
2990
3250
  };
2991
3251
  await this.saveTeamToProjectAuthTokens({
2992
3252
  tenantId: tenantId,
2993
3253
  team: installedTeam,
2994
3254
  });
2995
- logger.debug(`Captured Microsoft Teams team install ${teamId} for tenant ${tenantId}`);
3255
+ logger.debug(`Captured Microsoft Teams team install ${installedTeam.id} (thread ${teamsThreadId}, graph ${graphTeamId || "unresolved"}) for tenant ${tenantId}`);
2996
3256
  }
2997
3257
  catch (err) {
2998
3258
  logger.error("Error capturing Microsoft Teams team from bot activity:");
@@ -3000,19 +3260,28 @@ All monitoring checks are passing normally.`;
3000
3260
  }
3001
3261
  }
3002
3262
  static async removeTeamFromBotActivity(data) {
3003
- var _a, _b;
3263
+ var _a;
3004
3264
  try {
3005
3265
  const channelData = data.activity["channelData"] || {};
3006
- const teamId = ((_a = channelData["team"]) === null || _a === void 0 ? void 0 : _a["id"]) || "";
3007
- const tenantId = ((_b = channelData["tenant"]) === null || _b === void 0 ? void 0 : _b["id"]) || "";
3008
- if (!teamId || !tenantId) {
3266
+ const team = channelData["team"];
3267
+ const teamsThreadId = (team === null || team === void 0 ? void 0 : team["id"]) || "";
3268
+ const tenantId = ((_a = channelData["tenant"]) === null || _a === void 0 ? void 0 : _a["id"]) || "";
3269
+ if (!teamsThreadId || !tenantId) {
3009
3270
  return;
3010
3271
  }
3272
+ /*
3273
+ * Uninstall matches on both ids because we cannot know which one the
3274
+ * record was keyed by: the bot is already out of the team, so
3275
+ * getTeamDetails will not answer, and records written before this shipped
3276
+ * only have a thread id. aadGroupId is still on the activity often enough
3277
+ * to be worth passing through.
3278
+ */
3011
3279
  await this.removeTeamFromProjectAuthTokens({
3012
3280
  tenantId: tenantId,
3013
- teamId: teamId,
3281
+ teamsThreadId: teamsThreadId,
3282
+ graphTeamId: (team === null || team === void 0 ? void 0 : team["aadGroupId"]) || undefined,
3014
3283
  });
3015
- logger.debug(`Removed Microsoft Teams team install ${teamId} for tenant ${tenantId}`);
3284
+ logger.debug(`Removed Microsoft Teams team install ${teamsThreadId} for tenant ${tenantId}`);
3016
3285
  }
3017
3286
  catch (err) {
3018
3287
  logger.error("Error removing Microsoft Teams team from bot activity:");
@@ -3042,7 +3311,25 @@ All monitoring checks are passing normally.`;
3042
3311
  });
3043
3312
  for (const projectAuth of projectAuths) {
3044
3313
  const miscData = Object.assign({}, (projectAuth.miscData || {}));
3045
- miscData.installedTeams = Object.assign(Object.assign({}, (miscData.installedTeams || {})), { [data.team.id]: data.team });
3314
+ /*
3315
+ * Drop any other record describing the same team before inserting. A
3316
+ * team captured before the group id was resolvable sits under its thread
3317
+ * id, and re-capturing it under the group id would otherwise leave two
3318
+ * records for one team — one of which can never be matched or removed.
3319
+ */
3320
+ const installedTeams = {};
3321
+ for (const [key, existingTeam] of Object.entries(miscData.installedTeams || {})) {
3322
+ if (this.isSameInstalledTeam({
3323
+ team: existingTeam,
3324
+ graphTeamId: data.team.graphTeamId,
3325
+ teamsThreadId: data.team.teamsThreadId,
3326
+ })) {
3327
+ continue;
3328
+ }
3329
+ installedTeams[key] = existingTeam;
3330
+ }
3331
+ installedTeams[data.team.id] = data.team;
3332
+ miscData.installedTeams = installedTeams;
3046
3333
  await WorkspaceProjectAuthTokenService.updateOneById({
3047
3334
  id: projectAuth.id,
3048
3335
  data: {
@@ -3054,6 +3341,28 @@ All monitoring checks are passing normally.`;
3054
3341
  });
3055
3342
  }
3056
3343
  }
3344
+ /*
3345
+ * Whether a stored record describes the team identified by either id. Matches
3346
+ * on the record's explicit ids and on its key, so records written before
3347
+ * teamsThreadId existed — where the thread id is only in `id` — still match.
3348
+ * Ids are compared case-sensitively, as Microsoft returns them.
3349
+ */
3350
+ static isSameInstalledTeam(data) {
3351
+ if (!data.team) {
3352
+ return false;
3353
+ }
3354
+ if (data.graphTeamId &&
3355
+ (data.team.graphTeamId === data.graphTeamId ||
3356
+ data.team.id === data.graphTeamId)) {
3357
+ return true;
3358
+ }
3359
+ if (data.teamsThreadId &&
3360
+ (data.team.teamsThreadId === data.teamsThreadId ||
3361
+ data.team.id === data.teamsThreadId)) {
3362
+ return true;
3363
+ }
3364
+ return false;
3365
+ }
3057
3366
  static async removeTeamFromProjectAuthTokens(data) {
3058
3367
  const projectAuths = await WorkspaceProjectAuthTokenService.findBy({
3059
3368
  query: {
@@ -3072,11 +3381,25 @@ All monitoring checks are passing normally.`;
3072
3381
  });
3073
3382
  for (const projectAuth of projectAuths) {
3074
3383
  const miscData = Object.assign({}, (projectAuth.miscData || {}));
3075
- if (!miscData.installedTeams || !miscData.installedTeams[data.teamId]) {
3384
+ if (!miscData.installedTeams) {
3385
+ continue;
3386
+ }
3387
+ const installedTeams = {};
3388
+ let removedAny = false;
3389
+ for (const [key, existingTeam] of Object.entries(miscData.installedTeams)) {
3390
+ if (this.isSameInstalledTeam({
3391
+ team: existingTeam,
3392
+ graphTeamId: data.graphTeamId,
3393
+ teamsThreadId: data.teamsThreadId,
3394
+ })) {
3395
+ removedAny = true;
3396
+ continue;
3397
+ }
3398
+ installedTeams[key] = existingTeam;
3399
+ }
3400
+ if (!removedAny) {
3076
3401
  continue;
3077
3402
  }
3078
- const installedTeams = Object.assign({}, miscData.installedTeams);
3079
- delete installedTeams[data.teamId];
3080
3403
  miscData.installedTeams = installedTeams;
3081
3404
  await WorkspaceProjectAuthTokenService.updateOneById({
3082
3405
  id: projectAuth.id,
@@ -3715,6 +4038,12 @@ __decorate([
3715
4038
  __metadata("design:paramtypes", [Object]),
3716
4039
  __metadata("design:returntype", Promise)
3717
4040
  ], MicrosoftTeamsUtil, "sendAdaptiveCardToChannel", null);
4041
+ __decorate([
4042
+ CaptureSpan(),
4043
+ __metadata("design:type", Function),
4044
+ __metadata("design:paramtypes", [Object]),
4045
+ __metadata("design:returntype", Promise)
4046
+ ], MicrosoftTeamsUtil, "isAppInstalledInTeam", null);
3718
4047
  __decorate([
3719
4048
  CaptureSpan(),
3720
4049
  __metadata("design:type", Function),