@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
@@ -58,6 +58,32 @@ export type DbTypes =
58
58
  | JSONArray
59
59
  | Buffer;
60
60
 
61
+ /*
62
+ * Metadata-only vanilla instances used by toJSONObject to read table-column
63
+ * metadata. Frozen and never exported, so they can never be mutated or escape
64
+ * this file. _fromJSON must never use this cache — it mutates and returns the
65
+ * instance it constructs.
66
+ */
67
+ const vanillaModelCache: WeakMap<
68
+ { new (): DatabaseBaseModel },
69
+ DatabaseBaseModel
70
+ > = new WeakMap();
71
+
72
+ function getVanillaModel(modelType: {
73
+ new (): DatabaseBaseModel;
74
+ }): DatabaseBaseModel {
75
+ let vanillaModel: DatabaseBaseModel | undefined =
76
+ vanillaModelCache.get(modelType);
77
+
78
+ if (!vanillaModel) {
79
+ vanillaModel = new modelType();
80
+ Object.freeze(vanillaModel);
81
+ vanillaModelCache.set(modelType, vanillaModel);
82
+ }
83
+
84
+ return vanillaModel;
85
+ }
86
+
61
87
  export default class DatabaseBaseModel extends BaseEntity {
62
88
  @TableColumn({
63
89
  title: "ID",
@@ -252,8 +278,7 @@ export default class DatabaseBaseModel extends BaseEntity {
252
278
  }
253
279
 
254
280
  public getTableColumnMetadata(columnName: string): TableColumnMetadata {
255
- const dictionary: Dictionary<TableColumnMetadata> = getTableColumns(this);
256
- return dictionary[columnName] as TableColumnMetadata;
281
+ return getTableColumn(this, columnName);
257
282
  }
258
283
 
259
284
  public hasColumn(columnName: string): boolean {
@@ -275,8 +300,10 @@ export default class DatabaseBaseModel extends BaseEntity {
275
300
  }
276
301
 
277
302
  public getColumnAccessControlForAllColumns(): Dictionary<ColumnAccessControl> {
278
- const dictionary: Dictionary<ColumnAccessControl> =
279
- getColumnAccessControlForAllColumns(this);
303
+ // Copy: the helper's dictionary is cached per class and must stay pristine.
304
+ const dictionary: Dictionary<ColumnAccessControl> = {
305
+ ...getColumnAccessControlForAllColumns(this),
306
+ };
280
307
 
281
308
  const defaultColumns: Array<string> = [
282
309
  "_id",
@@ -613,7 +640,7 @@ export default class DatabaseBaseModel extends BaseEntity {
613
640
  ): JSONObject {
614
641
  const json: JSONObject = {};
615
642
 
616
- const vanillaModel: DatabaseBaseModel = new modelType();
643
+ const vanillaModel: DatabaseBaseModel = getVanillaModel(modelType);
617
644
 
618
645
  for (const key of vanillaModel.getTableColumns().columns) {
619
646
  if ((model as any)[key] === undefined) {
@@ -1942,6 +1942,70 @@ export default class NetworkDevice extends BaseModel {
1942
1942
  })
1943
1943
  public lastSeenAt?: Date = undefined;
1944
1944
 
1945
+ @ColumnAccessControl({
1946
+ create: [],
1947
+ read: [
1948
+ Permission.ProjectOwner,
1949
+ Permission.ProjectAdmin,
1950
+ Permission.ProjectMember,
1951
+ Permission.Viewer,
1952
+ Permission.SettingsAdmin,
1953
+ Permission.SettingsMember,
1954
+ Permission.SettingsViewer,
1955
+ Permission.ReadNetworkDevice,
1956
+ ],
1957
+ update: [
1958
+ Permission.ProjectOwner,
1959
+ Permission.ProjectAdmin,
1960
+ Permission.EditNetworkDevice,
1961
+ ],
1962
+ })
1963
+ @TableColumn({
1964
+ required: false,
1965
+ type: TableColumnType.Date,
1966
+ canReadOnRelationQuery: true,
1967
+ title: "Last Polled At",
1968
+ description:
1969
+ 'When the assigned probe last ATTEMPTED an SNMP walk of this device, whether or not the device answered. Paired with lastSeenAt (which only moves on a successful walk) this is what separates "the device did not answer" from "we have not asked recently". Managed by the probe.',
1970
+ })
1971
+ @Column({
1972
+ nullable: true,
1973
+ type: ColumnType.Date,
1974
+ })
1975
+ public lastPolledAt?: Date = undefined;
1976
+
1977
+ @ColumnAccessControl({
1978
+ create: [],
1979
+ read: [
1980
+ Permission.ProjectOwner,
1981
+ Permission.ProjectAdmin,
1982
+ Permission.ProjectMember,
1983
+ Permission.Viewer,
1984
+ Permission.SettingsAdmin,
1985
+ Permission.SettingsMember,
1986
+ Permission.SettingsViewer,
1987
+ Permission.ReadNetworkDevice,
1988
+ ],
1989
+ update: [
1990
+ Permission.ProjectOwner,
1991
+ Permission.ProjectAdmin,
1992
+ Permission.EditNetworkDevice,
1993
+ ],
1994
+ })
1995
+ @TableColumn({
1996
+ required: false,
1997
+ type: TableColumnType.Boolean,
1998
+ canReadOnRelationQuery: true,
1999
+ title: "Is Reachable",
2000
+ description:
2001
+ "Whether the most recent SNMP walk reached this device. NULL means it has never been polled. This — not the age of lastSeenAt — is what the device list, the topology graph and the site rollup read, so a device whose last poll succeeded is never shown as down just because the probe is behind schedule. Managed by the probe.",
2002
+ })
2003
+ @Column({
2004
+ nullable: true,
2005
+ type: ColumnType.Boolean,
2006
+ })
2007
+ public isReachable?: boolean = undefined;
2008
+
1945
2009
  @ColumnAccessControl({
1946
2010
  create: [],
1947
2011
  read: [
@@ -317,6 +317,65 @@ export default class StatusPage extends BaseModel {
317
317
  })
318
318
  public pageDescription?: string = undefined;
319
319
 
320
+ /*
321
+ * Search engine indexing opt-out. Indexing stays on unless the owner turns
322
+ * it off, so this defaults to true and every pre-existing status page keeps
323
+ * being indexed exactly as it is today.
324
+ *
325
+ * Turning it off is served two ways, both of which a crawler honours before
326
+ * it decides to index: a <meta name="robots" content="noindex, nofollow">
327
+ * in the server-rendered index.ejs (so it is there before the JS bundle
328
+ * loads) and an X-Robots-Tag response header (so it also covers the RSS
329
+ * feed and llms.txt, which are not HTML).
330
+ *
331
+ * Deliberately NOT wired into robots.txt: Disallow blocks the crawl, and a
332
+ * crawler that never fetches the page never sees the noindex, so the URL can
333
+ * still surface in results. Letting crawlers in and telling them not to
334
+ * index is what actually removes the page.
335
+ */
336
+ @ColumnAccessControl({
337
+ create: [
338
+ Permission.ProjectOwner,
339
+ Permission.ProjectAdmin,
340
+ Permission.ProjectMember,
341
+ Permission.StatusPageAdmin,
342
+ Permission.StatusPageMember,
343
+ Permission.CreateProjectStatusPage,
344
+ ],
345
+ read: [
346
+ Permission.ProjectOwner,
347
+ Permission.ProjectAdmin,
348
+ Permission.ProjectMember,
349
+ Permission.Viewer,
350
+ Permission.StatusPageAdmin,
351
+ Permission.StatusPageMember,
352
+ Permission.StatusPageViewer,
353
+ Permission.ReadProjectStatusPage,
354
+ ],
355
+ update: [
356
+ Permission.ProjectOwner,
357
+ Permission.ProjectAdmin,
358
+ Permission.ProjectMember,
359
+ Permission.StatusPageAdmin,
360
+ Permission.StatusPageMember,
361
+ Permission.EditProjectStatusPage,
362
+ ],
363
+ })
364
+ @TableColumn({
365
+ isDefaultValueColumn: true,
366
+ type: TableColumnType.Boolean,
367
+ title: "Enable Search Engine Indexing",
368
+ description:
369
+ "Should search engines like Google and Bing be allowed to index this status page? Turn this off to keep the page reachable by link but out of search results.",
370
+ defaultValue: true,
371
+ })
372
+ @Column({
373
+ type: ColumnType.Boolean,
374
+ default: true,
375
+ nullable: false,
376
+ })
377
+ public enableSearchEngineIndexing?: boolean = undefined;
378
+
320
379
  @ColumnAccessControl({
321
380
  create: [
322
381
  Permission.ProjectOwner,
@@ -14,6 +14,7 @@ import CanAccessIfCanReadOn from "../../Types/Database/CanAccessIfCanReadOn";
14
14
  import ColumnLength from "../../Types/Database/ColumnLength";
15
15
  import ColumnType from "../../Types/Database/ColumnType";
16
16
  import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
17
+ import EnableDocumentation from "../../Types/Database/EnableDocumentation";
17
18
  import TableColumn from "../../Types/Database/TableColumn";
18
19
  import TableColumnType from "../../Types/Database/TableColumnType";
19
20
  import TableMetadata from "../../Types/Database/TableMetadata";
@@ -24,6 +25,7 @@ import ObjectID from "../../Types/ObjectID";
24
25
  import Permission from "../../Types/Permission";
25
26
  import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
26
27
 
28
+ @EnableDocumentation()
27
29
  @CanAccessIfCanReadOn("statusPage")
28
30
  @TableEditionAccessControl({
29
31
  requiresEnterprise: true,
@@ -45,9 +45,26 @@ export interface MicrosoftTeamsChat {
45
45
  * roster member of. Listing a team is therefore NOT evidence that we can post
46
46
  * to it — this record is, which is why channel sends check it before calling
47
47
  * the Bot Framework.
48
+ *
49
+ * A Teams team has TWO ids and they are not interchangeable. Bot activities
50
+ * carry the thread id (channelData.team.id, "19:...@thread.tacv2"); Graph — and
51
+ * therefore every team picker, notification rule and channel URL in OneUptime —
52
+ * uses the AAD group id (a GUID, channelData.team.aadGroupId). Records used to
53
+ * be keyed by the thread id and looked up by the group id, so the lookup could
54
+ * never hit. Both are stored explicitly now so neither side has to guess which
55
+ * one it holds.
48
56
  */
49
57
  export interface MicrosoftTeamsInstalledTeam {
50
- id: string; // Teams team (group) id.
58
+ /*
59
+ * Map key. The Graph team id when we could resolve it, otherwise the Teams
60
+ * thread id — a record written before this shipped, or one whose group id
61
+ * could not be resolved, keeps the thread id here so uninstall still matches.
62
+ * Read it through MicrosoftTeamsUtil.indexInstalledTeamsByGraphTeamId rather
63
+ * than assuming which id space it is in.
64
+ */
65
+ id: string;
66
+ graphTeamId?: string | undefined; // AAD group id — what Graph and notification rules use. Absent on unresolved records.
67
+ teamsThreadId?: string | undefined; // channelData.team.id ("19:...@thread.tacv2") — what bot activities carry.
51
68
  name?: string | undefined;
52
69
  serviceUrl?: string | undefined; // Bot Framework service URL captured on install. Required for GCC/DoD.
53
70
  addedAt?: string | undefined;
@@ -79,7 +96,7 @@ export interface MicrosoftTeamsMiscData extends MiscData {
79
96
  availableTeams?: Record<string, MicrosoftTeamsTeam>; // keyed by team id. Every team Graph can see in the tenant.
80
97
  appAccessTokenExpiresAt?: string;
81
98
  availableChats?: Record<string, MicrosoftTeamsChat>; // keyed by chat id. Chats the OneUptime app has been added to.
82
- installedTeams?: Record<string, MicrosoftTeamsInstalledTeam>; // keyed by team id. Teams the OneUptime app has been added to.
99
+ installedTeams?: Record<string, MicrosoftTeamsInstalledTeam>; // keyed by MicrosoftTeamsInstalledTeam.id see that type, the key is not always a Graph team id.
83
100
  }
84
101
 
85
102
  export type WorkspaceMiscData = SlackMiscData | MicrosoftTeamsMiscData;
@@ -9,7 +9,6 @@ import Express, {
9
9
  import Response from "../Utils/Response";
10
10
  import BadDataException from "../../Types/Exception/BadDataException";
11
11
  import PaymentRequiredException from "../../Types/Exception/PaymentRequiredException";
12
- import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
13
12
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
14
13
  import ObjectID from "../../Types/ObjectID";
15
14
  import OneUptimeDate from "../../Types/Date";
@@ -89,18 +88,16 @@ router.post(
89
88
  const props: DatabaseCommonInteractionProps =
90
89
  await CommonAPI.getDatabaseCommonInteractionProps(req);
91
90
 
92
- if (!props.userId) {
93
- throw new NotAuthorizedException(
94
- "AI chat requires a logged-in user session.",
95
- );
96
- }
97
-
98
- if (!props.tenantId) {
99
- throw new BadDataException("Project ID is required (tenantid header).");
100
- }
101
-
102
- const projectId: ObjectID = props.tenantId;
103
- const userId: ObjectID = props.userId;
91
+ /*
92
+ * Membership, not just a session. getUserMiddleware admits an
93
+ * unauthenticated request as "public" and reads the tenant id straight
94
+ * out of the caller-supplied `tenantid` header, so the pair of checks
95
+ * this replaces proved only that SOMEBODY was logged in and had named
96
+ * SOME project - never that they belonged to the project they named.
97
+ */
98
+ const projectId: ObjectID =
99
+ CommonAPI.assertAuthenticatedProjectMember(props);
100
+ const userId: ObjectID = props.userId!;
104
101
 
105
102
  const content: string = (req.body["content"] as string) || "";
106
103
 
@@ -502,17 +499,30 @@ router.post(
502
499
  const props: DatabaseCommonInteractionProps =
503
500
  await CommonAPI.getDatabaseCommonInteractionProps(req);
504
501
 
505
- if (!props.userId) {
506
- throw new NotAuthorizedException(
507
- "AI chat requires a logged-in user session.",
508
- );
509
- }
510
-
511
- if (!props.tenantId) {
512
- throw new BadDataException("Project ID is required (tenantid header).");
513
- }
502
+ /*
503
+ * Membership, not just a session - see the note on send-message. This
504
+ * route is where that distinction was load-bearing: it took the project
505
+ * id straight from the header and handed it to root-privileged reads,
506
+ * so any logged-in user could name ANY project's UUID and be told which
507
+ * LLM providers that project has - their names, descriptions, types and
508
+ * models (GHSA-hm7m-9qjj-xj5x).
509
+ */
510
+ const projectId: ObjectID =
511
+ CommonAPI.assertAuthenticatedProjectMember(props);
514
512
 
515
- const projectId: ObjectID = props.tenantId;
513
+ /*
514
+ * Membership is still not read authorisation. The very same rows served
515
+ * by a plain GET /llm-provider are gated there by LlmProvider's own read
516
+ * list, and a member whose teams grant none of those permissions has no
517
+ * business reading them through the chat picker instead. Asking the
518
+ * model for that list keeps the two paths from drifting apart.
519
+ */
520
+ CommonAPI.assertPermittedInProject({
521
+ databaseProps: props,
522
+ allowedPermissions: new LlmProvider().getReadPermissions(),
523
+ errorMessage:
524
+ "You do not have permission to read this project's AI providers.",
525
+ });
516
526
 
517
527
  const [providers, defaultProvider]: [
518
528
  Array<LlmProvider>,
@@ -561,18 +571,16 @@ router.post(
561
571
  const props: DatabaseCommonInteractionProps =
562
572
  await CommonAPI.getDatabaseCommonInteractionProps(req);
563
573
 
564
- if (!props.userId) {
565
- throw new NotAuthorizedException(
566
- "AI chat requires a logged-in user session.",
567
- );
568
- }
569
-
570
- if (!props.tenantId) {
571
- throw new BadDataException("Project ID is required (tenantid header).");
572
- }
573
-
574
- const projectId: ObjectID = props.tenantId;
575
- const userId: ObjectID = props.userId;
574
+ /*
575
+ * Membership, not just a session. getUserMiddleware admits an
576
+ * unauthenticated request as "public" and reads the tenant id straight
577
+ * out of the caller-supplied `tenantid` header, so the pair of checks
578
+ * this replaces proved only that SOMEBODY was logged in and had named
579
+ * SOME project - never that they belonged to the project they named.
580
+ */
581
+ const projectId: ObjectID =
582
+ CommonAPI.assertAuthenticatedProjectMember(props);
583
+ const userId: ObjectID = props.userId!;
576
584
 
577
585
  const conversationIdString: string = req.body["conversationId"] as string;
578
586
  const assistantMessageIdString: string = req.body[
@@ -743,18 +751,16 @@ router.post(
743
751
  const props: DatabaseCommonInteractionProps =
744
752
  await CommonAPI.getDatabaseCommonInteractionProps(req);
745
753
 
746
- if (!props.userId) {
747
- throw new NotAuthorizedException(
748
- "AI chat requires a logged-in user session.",
749
- );
750
- }
751
-
752
- if (!props.tenantId) {
753
- throw new BadDataException("Project ID is required (tenantid header).");
754
- }
755
-
756
- const projectId: ObjectID = props.tenantId;
757
- const userId: ObjectID = props.userId;
754
+ /*
755
+ * Membership, not just a session. getUserMiddleware admits an
756
+ * unauthenticated request as "public" and reads the tenant id straight
757
+ * out of the caller-supplied `tenantid` header, so the pair of checks
758
+ * this replaces proved only that SOMEBODY was logged in and had named
759
+ * SOME project - never that they belonged to the project they named.
760
+ */
761
+ const projectId: ObjectID =
762
+ CommonAPI.assertAuthenticatedProjectMember(props);
763
+ const userId: ObjectID = props.userId!;
758
764
 
759
765
  const conversationIdString: string = req.body["conversationId"] as string;
760
766
 
@@ -913,15 +919,8 @@ router.post(
913
919
  const props: DatabaseCommonInteractionProps =
914
920
  await CommonAPI.getDatabaseCommonInteractionProps(req);
915
921
 
916
- if (!props.userId) {
917
- throw new NotAuthorizedException(
918
- "AI chat requires a logged-in user session.",
919
- );
920
- }
921
-
922
- if (!props.tenantId) {
923
- throw new BadDataException("Project ID is required (tenantid header).");
924
- }
922
+ // Membership, not just a session - see the note on send-message.
923
+ CommonAPI.assertAuthenticatedProjectMember(props);
925
924
 
926
925
  const messageIdString: string = req.body["messageId"] as string;
927
926
 
@@ -9,6 +9,14 @@ import SpanUtil from "../Utils/Telemetry/SpanUtil";
9
9
  import ObjectID from "../../Types/ObjectID";
10
10
  import BadDataException from "../../Types/Exception/BadDataException";
11
11
  import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
12
+ import Permission, {
13
+ PermissionHelper,
14
+ PermissionProps,
15
+ UserPermission,
16
+ } from "../../Types/Permission";
17
+ import DatabaseCommonInteractionPropsUtil, {
18
+ PermissionType,
19
+ } from "../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
12
20
 
13
21
  export default class CommonAPI {
14
22
  /*
@@ -132,6 +140,87 @@ export default class CommonAPI {
132
140
  }
133
141
  }
134
142
 
143
+ /*
144
+ * Membership is not read authorisation.
145
+ *
146
+ * assertAuthenticatedProjectMember proves the caller belongs to the project
147
+ * they named in the `tenantid` header; it says nothing about WHICH of that
148
+ * project's data they may see. A custom route that then reads with
149
+ * `isRoot: true` has skipped the permission check the equivalent CRUD read
150
+ * would have run, so the route has to make that check itself - otherwise a
151
+ * member whose teams grant nothing but, say, ReadProjectIncident still reads
152
+ * everything else the route happens to touch.
153
+ *
154
+ * The permissions consulted are the caller's grants in props.tenantId, so
155
+ * this must be called after assertAuthenticatedProjectMember has confirmed
156
+ * that tenant is one the caller actually belongs to.
157
+ *
158
+ * `allowedPermissions` is normally the model's own declared list, e.g.
159
+ * `new LlmProvider().getReadPermissions()`, so the custom route stays in
160
+ * step with the CRUD endpoint for the same data instead of inventing a
161
+ * second, quietly diverging rule.
162
+ *
163
+ * Permissions that are never assigned INSIDE a project are stripped from
164
+ * that list first. This matters: getUserPermissions merges the caller's
165
+ * global permissions in, and every caller - including an anonymous one -
166
+ * carries Permission.Public, which a model's read list very often contains
167
+ * (LlmProvider has it so the shared global providers stay readable). Left
168
+ * in, it would make this guard pass for anyone. What remains is the
169
+ * tenant-assignable subset, which is exactly what a team can grant.
170
+ *
171
+ * Stripping can empty the list, and an empty list denies everyone rather
172
+ * than admitting them - a model whose read access is purely public has no
173
+ * business behind this guard, and failing closed is the safe direction.
174
+ *
175
+ * Master admins bypass, matching every other permission gate in the API.
176
+ */
177
+ public static assertPermittedInProject(data: {
178
+ databaseProps: DatabaseCommonInteractionProps;
179
+ allowedPermissions: Array<Permission>;
180
+ errorMessage?: string | undefined;
181
+ }): void {
182
+ if (data.databaseProps.isMasterAdmin) {
183
+ return;
184
+ }
185
+
186
+ const tenantAssignablePermissions: Array<Permission> =
187
+ PermissionHelper.getTenantPermissionProps().map(
188
+ (permissionProps: PermissionProps) => {
189
+ return permissionProps.permission;
190
+ },
191
+ );
192
+
193
+ const requiredPermissions: Array<Permission> =
194
+ data.allowedPermissions.filter((permission: Permission) => {
195
+ return tenantAssignablePermissions.includes(permission);
196
+ });
197
+
198
+ /*
199
+ * Allow-only: getUserPermissions discriminates grants from denials by
200
+ * isBlockPermission, so a team's explicit BLOCK row for one of these
201
+ * permissions must never be counted as a grant of it.
202
+ */
203
+ const grantedPermissions: Array<Permission> =
204
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
205
+ data.databaseProps,
206
+ PermissionType.Allow,
207
+ ).map((userPermission: UserPermission) => {
208
+ return userPermission.permission;
209
+ });
210
+
211
+ if (
212
+ !PermissionHelper.doesPermissionsIntersect(
213
+ grantedPermissions,
214
+ requiredPermissions,
215
+ )
216
+ ) {
217
+ throw new NotAuthorizedException(
218
+ data.errorMessage ||
219
+ "You do not have permission to access this project's data.",
220
+ );
221
+ }
222
+ }
223
+
135
224
  @CaptureSpan()
136
225
  public static async getDatabaseCommonInteractionProps(
137
226
  req: ExpressRequest,
@@ -177,6 +177,16 @@ export default class MicrosoftTeamsAPI {
177
177
  type: "Application",
178
178
  name: "ChatMember.Read.Chat",
179
179
  },
180
+ /*
181
+ * Lets OneUptime confirm, per team, that the installed OneUptime app
182
+ * is the package built from THIS deployment before telling an admin
183
+ * their app is missing. Without it the installed-apps read falls back
184
+ * to "unknown" and the send is handed to Microsoft to reject.
185
+ */
186
+ {
187
+ type: "Application",
188
+ name: "TeamsAppInstallation.Read.Group",
189
+ },
180
190
  ],
181
191
  },
182
192
  },
@@ -386,12 +396,13 @@ export default class MicrosoftTeamsAPI {
386
396
  "https://graph.microsoft.com/User.Read https://graph.microsoft.com/Team.ReadBasic.All https://graph.microsoft.com/Channel.ReadBasic.All https://graph.microsoft.com/ChannelMessage.Send",
387
397
  };
388
398
 
399
+ /*
400
+ * The token request body holds the app client secret and the
401
+ * authorization code, and the response holds the user access and
402
+ * refresh tokens -- neither is logged.
403
+ */
389
404
  logger.debug(
390
- "Microsoft Teams Token Request Body (static redirect): ",
391
- getLogAttributesFromRequest(req as any),
392
- );
393
- logger.debug(
394
- tokenRequestBody,
405
+ "Exchanging Microsoft Teams authorization code for an access token.",
395
406
  getLogAttributesFromRequest(req as any),
396
407
  );
397
408
 
@@ -420,10 +431,9 @@ export default class MicrosoftTeamsAPI {
420
431
 
421
432
  const tokenData: JSONObject = tokenResponse.data;
422
433
  logger.debug(
423
- "Microsoft Teams Token Response (static redirect): ",
434
+ "Microsoft Teams token exchange completed.",
424
435
  getLogAttributesFromRequest(req as any),
425
436
  );
426
- logger.debug(tokenData, getLogAttributesFromRequest(req as any));
427
437
 
428
438
  if (!tokenData["access_token"]) {
429
439
  return Response.sendErrorResponse(
@@ -725,11 +735,12 @@ export default class MicrosoftTeamsAPI {
725
735
  Date.now() + Math.max(0, (expiresInSec - 60) * 1000),
726
736
  ).toISOString();
727
737
 
738
+ // tokenData carries the app access token; only its expiry is logged.
728
739
  logger.debug(
729
- "App Access Token acquired via admin consent: ",
740
+ "Microsoft Graph app token acquired via admin consent. expiresAt: " +
741
+ expiresAtIso,
730
742
  getLogAttributesFromRequest(req as any),
731
743
  );
732
- logger.debug(tokenData, getLogAttributesFromRequest(req as any));
733
744
 
734
745
  // Get available teams from user auth token
735
746
  const userId: string = stateParts[1]!;
@@ -164,11 +164,15 @@ export default class SlackAPI {
164
164
  redirect_uri: redirectUri.toString(),
165
165
  };
166
166
 
167
+ /*
168
+ * Neither the token request nor the token response is logged: the
169
+ * request carries the app client secret and the authorization code,
170
+ * and the response carries the bot and user access tokens.
171
+ */
167
172
  logger.debug(
168
- "Slack Auth Request Body: ",
173
+ "Exchanging Slack authorization code for an access token.",
169
174
  getLogAttributesFromRequest(req as any),
170
175
  );
171
- logger.debug(requestBody, getLogAttributesFromRequest(req as any));
172
176
 
173
177
  // send the request to slack api to get the access token https://slack.com/api/oauth.v2.access
174
178
 
@@ -188,10 +192,9 @@ export default class SlackAPI {
188
192
  const responseBody: JSONObject = response.data;
189
193
 
190
194
  logger.debug(
191
- "Slack Auth Request Body: ",
195
+ "Slack token exchange completed. ok: " + String(responseBody["ok"]),
192
196
  getLogAttributesFromRequest(req as any),
193
197
  );
194
- logger.debug(responseBody, getLogAttributesFromRequest(req as any));
195
198
 
196
199
  let slackTeamId: string | undefined = undefined;
197
200
  let slackBotAccessToken: string | undefined = undefined;
@@ -384,11 +387,11 @@ export default class SlackAPI {
384
387
  redirect_uri: redirectUri.toString(),
385
388
  };
386
389
 
390
+ // Same as above: the body holds the client secret and the auth code.
387
391
  logger.debug(
388
- "Slack Auth Request Body: ",
392
+ "Exchanging Slack authorization code for a user token.",
389
393
  getLogAttributesFromRequest(req as any),
390
394
  );
391
- logger.debug(requestBody, getLogAttributesFromRequest(req as any));
392
395
 
393
396
  // send the request to slack api to get the access token https://slack.com/api/oauth.v2.access
394
397
 
@@ -408,10 +411,10 @@ export default class SlackAPI {
408
411
  const responseBody: JSONObject = response.data;
409
412
 
410
413
  logger.debug(
411
- "Slack User Auth Request Body: ",
414
+ "Slack user token exchange completed. ok: " +
415
+ String(responseBody["ok"]),
412
416
  getLogAttributesFromRequest(req as any),
413
417
  );
414
- logger.debug(responseBody, getLogAttributesFromRequest(req as any));
415
418
 
416
419
  if (
417
420
  responseBody["id_token"] &&
@@ -425,11 +428,11 @@ export default class SlackAPI {
425
428
  "base64",
426
429
  ).toString("utf8"),
427
430
  );
431
+ // The decoded ID token is identity material; only the fact is logged.
428
432
  logger.debug(
429
- "Decoded ID Token: ",
433
+ "Decoded Slack ID token.",
430
434
  getLogAttributesFromRequest(req as any),
431
435
  );
432
- logger.debug(decodedIdToken, getLogAttributesFromRequest(req as any));
433
436
  responseBody["id_token"] = decodedIdToken;
434
437
  }
435
438