@oneuptime/common 12.0.28 → 12.0.30

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 (367) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  2. package/Models/DatabaseModels/Index.ts +11 -0
  3. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  4. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  7. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  8. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  9. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  10. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  11. package/Server/API/DashboardDomainAPI.ts +74 -0
  12. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  13. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  14. package/Server/API/StatusPageDomainAPI.ts +74 -0
  15. package/Server/EnvironmentConfig.ts +138 -0
  16. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  21. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  22. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  23. package/Server/Services/DashboardDomainService.ts +131 -0
  24. package/Server/Services/Index.ts +12 -0
  25. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  26. package/Server/Services/MonitorTemplateService.ts +70 -11
  27. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  28. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  29. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  30. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  31. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  32. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  33. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  34. package/Server/Services/StatusPageDomainService.ts +163 -3
  35. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  36. package/Server/Services/TeamMemberService.ts +489 -1
  37. package/Server/Services/TeamService.ts +50 -0
  38. package/Server/Services/UserNotificationSettingService.ts +44 -1
  39. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  40. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  41. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  42. package/Server/Types/Domain.ts +17 -0
  43. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  44. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  45. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  46. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  47. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  48. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  49. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  50. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  51. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  52. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  53. package/Server/Utils/Response.ts +134 -0
  54. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  55. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  56. package/Server/Utils/StartServer.ts +10 -0
  57. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  60. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  61. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  63. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  64. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  65. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  66. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  67. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  68. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  69. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  70. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  71. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  72. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  73. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  74. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  75. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  76. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  77. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  78. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  79. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  80. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  81. package/Tests/Server/API/Helpers.ts +8 -0
  82. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  83. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  84. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  85. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  86. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  87. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  88. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  89. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  90. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  91. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  92. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  93. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  94. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  95. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  96. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  97. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  98. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  99. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  100. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  101. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  102. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  103. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  104. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  105. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  106. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  107. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  108. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  109. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  110. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  111. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  112. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  113. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  114. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  115. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  116. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  117. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  118. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  119. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  120. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  121. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  122. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  123. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  124. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  125. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  126. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  127. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  128. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  129. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  130. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  131. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  132. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  133. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  134. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  135. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  137. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  138. package/Tests/Types/API/Route.test.ts +36 -4
  139. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  140. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  141. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  142. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  143. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  144. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  145. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  146. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  147. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  148. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  149. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  150. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  151. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  152. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  153. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  154. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  155. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  156. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  157. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  158. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  159. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  160. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  161. package/Tests/Utils/Array.test.ts +100 -0
  162. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  163. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  164. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  165. package/Types/API/Route.ts +20 -5
  166. package/Types/Calendar/ICalendar.ts +442 -0
  167. package/Types/Date.ts +143 -13
  168. package/Types/Email/EmailTemplateType.ts +2 -0
  169. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  170. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  171. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  172. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  173. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  174. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  175. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  176. package/Types/Rum/SessionReplay.ts +62 -1
  177. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  178. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  179. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  180. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  181. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  182. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  183. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  184. package/UI/Utils/Navigation.ts +42 -0
  185. package/Utils/Array.ts +37 -0
  186. package/Utils/CertificateReissue.ts +128 -0
  187. package/Utils/Grok/Grok.ts +461 -0
  188. package/Utils/Grok/GrokPatterns.ts +118 -0
  189. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  190. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  191. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  193. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  195. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  197. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  200. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  201. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  202. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  203. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  205. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  206. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  207. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  209. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  210. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  211. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  212. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  213. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  214. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  215. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  216. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  217. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  218. package/build/dist/Server/EnvironmentConfig.js +102 -0
  219. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  220. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  221. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  230. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  231. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  232. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  233. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  234. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  235. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  236. package/build/dist/Server/Services/Index.js +12 -0
  237. package/build/dist/Server/Services/Index.js.map +1 -1
  238. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  239. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  240. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  241. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  243. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  244. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  245. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  246. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  247. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  248. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  249. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  250. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  251. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  252. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  253. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  255. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  256. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  257. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  258. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  259. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  260. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  261. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  262. package/build/dist/Server/Services/TeamService.js +43 -0
  263. package/build/dist/Server/Services/TeamService.js.map +1 -1
  264. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  265. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  266. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  267. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  268. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  269. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  270. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  271. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  272. package/build/dist/Server/Types/Domain.js +16 -0
  273. package/build/dist/Server/Types/Domain.js.map +1 -1
  274. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  275. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  276. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  277. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  278. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  279. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  280. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  281. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  282. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  283. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  284. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  285. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  286. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  287. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  288. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  289. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  290. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  291. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  292. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  293. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  294. package/build/dist/Server/Utils/Response.js +97 -0
  295. package/build/dist/Server/Utils/Response.js.map +1 -1
  296. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  297. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  298. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  299. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  300. package/build/dist/Server/Utils/StartServer.js +9 -1
  301. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  302. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  303. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  304. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  314. package/build/dist/Types/API/Route.js +16 -5
  315. package/build/dist/Types/API/Route.js.map +1 -1
  316. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  317. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  318. package/build/dist/Types/Date.js +115 -13
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  321. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  322. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  323. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  324. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  325. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  326. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  327. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  328. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  329. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  330. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  331. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  332. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  333. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  334. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  335. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  336. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  337. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  338. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  339. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  340. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  341. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  342. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  343. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  344. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  345. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  346. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  347. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  349. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  350. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  351. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  352. package/build/dist/UI/Utils/Navigation.js +30 -0
  353. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  354. package/build/dist/Utils/Array.js +25 -0
  355. package/build/dist/Utils/Array.js.map +1 -1
  356. package/build/dist/Utils/CertificateReissue.js +97 -0
  357. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  358. package/build/dist/Utils/Grok/Grok.js +324 -0
  359. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  360. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  361. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  362. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  363. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  364. package/package.json +1 -1
  365. package/UI/Utils/JsonWebToken.ts +0 -14
  366. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  367. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -1,26 +1,149 @@
1
+ import { createPrivateKey } from "crypto";
1
2
  import jwt from "jsonwebtoken";
2
3
  import BadDataException from "../../../../Types/Exception/BadDataException";
3
4
  import APIException from "../../../../Types/Exception/ApiException";
5
+ import logger from "../../Logger";
4
6
  const CHRONICLE_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
5
7
  const TOKEN_LIFETIME_IN_SECONDS = 3600;
6
8
  const TOKEN_EXPIRY_SLACK_IN_SECONDS = 60;
9
+ /*
10
+ * Google rejects an assertion whose iat is in the future, and a host clock
11
+ * a few seconds fast is enough to trigger it — reported back as
12
+ * invalid_grant, which reads exactly like a bad key. Backdate iat instead.
13
+ * exp stays iat + 3600 because 3600 is Google's hard maximum lifetime, so
14
+ * this shortens the token's usable life rather than extending it.
15
+ */
16
+ const TOKEN_CLOCK_SKEW_IN_SECONDS = 60;
7
17
  const DEFAULT_MAX_ALERTS = 1000;
8
- const REGION_REGEX = /^[a-z][a-z0-9-]{0,30}$/;
9
- const INSTANCE_REGEX = /^projects\/[^/\s]+\/locations\/[^/\s]+\/instances\/[^/\s]+$/;
18
+ /*
19
+ * Neither fetch had a deadline before, and Node's global fetch has none of
20
+ * its own. Connections are polled strictly sequentially, so one endpoint
21
+ * that accepts a connection and never answers stalls every other tenant's
22
+ * poll behind it, outliving the cron's own timeout because that timeout
23
+ * does not abort the in-flight socket.
24
+ */
25
+ const REQUEST_TIMEOUT_IN_SECONDS = 60;
26
+ const REQUEST_TIMEOUT_IN_MS = REQUEST_TIMEOUT_IN_SECONDS * 1000;
27
+ /*
28
+ * How much of a body any diagnostic echoes. One bound for every echo site,
29
+ * because the integration doc and the connections page both quote a single
30
+ * figure — a second bound would make one of them wrong without anything
31
+ * saying so.
32
+ */
33
+ const BODY_ECHO_LIMIT = 500;
34
+ /*
35
+ * The 22 documented {region}-chronicle.googleapis.com prefixes. An
36
+ * allowlist rather than a shape regex because *.googleapis.com is a DNS
37
+ * wildcard: a typo like "us-central1" resolves to a Google frontend and
38
+ * answers with an HTML 404, so a regex that merely looks safe turns a
39
+ * misconfigured region into a parse error instead of "unsupported region".
40
+ */
41
+ const SUPPORTED_REGIONS = [
42
+ "us",
43
+ "eu",
44
+ "europe",
45
+ "africa-south1",
46
+ "asia-east1",
47
+ "asia-northeast1",
48
+ "asia-northeast3",
49
+ "asia-south1",
50
+ "asia-southeast1",
51
+ "asia-southeast2",
52
+ "australia-southeast1",
53
+ "europe-central2",
54
+ "europe-west12",
55
+ "europe-west2",
56
+ "europe-west3",
57
+ "europe-west6",
58
+ "europe-west9",
59
+ "me-central1",
60
+ "me-central2",
61
+ "me-west1",
62
+ "northamerica-northeast2",
63
+ "southamerica-east1",
64
+ ];
65
+ /*
66
+ * europe-chronicle.googleapis.com is a documented live host while the
67
+ * migration guide names the same multi-region's location code "eu", so the
68
+ * region/location cross-check has to treat the two as one place. A strict
69
+ * identity check would reject every valid EU tenant.
70
+ */
71
+ const EU_REGION_ALIASES = ["eu", "europe"];
72
+ /*
73
+ * The token endpoint is customer-supplied, and the first 500 characters of
74
+ * whatever answers it are echoed into lastError and rendered in the
75
+ * dashboard — a blind SSRF plus a read-back channel. Region and instance
76
+ * were always guarded; this one was not, which reads as an oversight
77
+ * rather than a decision.
78
+ */
79
+ const TOKEN_URI_HOSTS = ["accounts.google.com"];
80
+ const TOKEN_URI_HOST_SUFFIX = ".googleapis.com";
81
+ /*
82
+ * `#` truncates the path and drops the whole query string; `?` injects
83
+ * parameters ahead of the real ones; `%` lets a segment smuggle an encoded
84
+ * separator past this check. The host is fixed by getApiBaseUrl, so this is
85
+ * request shaping rather than URL takeover — still config-controlled
86
+ * injection, and still rejected.
87
+ */
88
+ const INSTANCE_REGEX = /^projects\/[^/\s#?&%]+\/locations\/[^/\s#?&%]+\/instances\/[^/\s#?&%]+$/;
89
+ const INSTANCE_LOCATION_REGEX = /^projects\/[^/]+\/locations\/([^/]+)\//;
90
+ /*
91
+ * The two 400s worth telling apart in the operator message: a field we sent
92
+ * that does not exist, versus a field we omitted that is required. Both are
93
+ * OneUptime bugs, but they have different fixes.
94
+ */
95
+ const UNKNOWN_FIELD_PATTERN = /cannot bind query parameter|unknown name/i;
96
+ const MISSING_FIELD_PATTERN = /required|missing/i;
97
+ /*
98
+ * The doc marks snapshotQuery `Required.`, but that is a field_behavior
99
+ * annotation the HTTP transcoder does not enforce; this service validates
100
+ * queries in-band (validSnapshotQuery / queryValidationErrors) rather than
101
+ * rejecting them. Fortinet's shipping connector omits it and gets 200, and
102
+ * the doc defines empty-snapshot-query semantics as "match all baseline".
103
+ * NOT verified against a live tenant.
104
+ *
105
+ * Deliberately NOT Google's SDK default `feedback_summary.status != "CLOSED"`
106
+ * — that drops every CLOSED alert, trading a loud 400 for silent data loss.
107
+ * If it turns out to be enforced, the 400 will name the missing field and
108
+ * this is the one line to change.
109
+ */
110
+ const SNAPSHOT_QUERY = null;
111
+ /*
112
+ * Every field a FetchAlertsViewResponse chunk may carry. A body in which no
113
+ * element carries at least one of these is not this endpoint's response,
114
+ * and must never be reported as "no alerts".
115
+ */
116
+ const RECOGNIZED_CHUNK_FIELDS = [
117
+ "alerts",
118
+ "fieldAggregations",
119
+ "complete",
120
+ "progress",
121
+ "tooManyAlerts",
122
+ "memoryLimitExceeded",
123
+ "validBaselineQuery",
124
+ "validSnapshotQuery",
125
+ "baselineAlertsCount",
126
+ "filteredAlertsCount",
127
+ "queryValidationErrors",
128
+ "runtimeErrors",
129
+ // Retained so a tenant already served by the legacy top-level shape keeps working.
130
+ "detections",
131
+ ];
10
132
  export default class GoogleSecOpsClient {
11
133
  constructor(data) {
12
134
  this.cachedAccessToken = null;
13
135
  this.cachedAccessTokenExpiresAtInMs = 0;
14
136
  GoogleSecOpsClient.validateRegion(data.region);
15
137
  GoogleSecOpsClient.validateInstanceResourceName(data.instanceResourceName);
138
+ GoogleSecOpsClient.validateRegionMatchesInstance(data.region, data.instanceResourceName);
16
139
  this.region = data.region;
17
- this.instanceResourceName = data.instanceResourceName;
140
+ this.instanceResourceName = GoogleSecOpsClient.encodeInstanceResourceName(data.instanceResourceName);
18
141
  this.credentials = GoogleSecOpsClient.parseServiceAccountJson(data.serviceAccountJson);
19
142
  this.fetchImplementation =
20
143
  data.fetchImplementation || fetch;
21
144
  }
22
145
  static validateRegion(region) {
23
- if (!REGION_REGEX.test(region || "")) {
146
+ if (SUPPORTED_REGIONS.indexOf(region || "") === -1) {
24
147
  throw new BadDataException("Region must be a Google SecOps regional prefix like 'us' or 'europe'.");
25
148
  }
26
149
  }
@@ -37,12 +160,31 @@ export default class GoogleSecOpsClient {
37
160
  catch (_a) {
38
161
  throw new BadDataException("Service account JSON is not valid JSON.");
39
162
  }
163
+ /*
164
+ * JSON.parse("null") and JSON.parse("[]") both succeed, and the reads
165
+ * below would then throw a raw TypeError out of a public create/update
166
+ * API — a 500 where the caller's input deserves a 400.
167
+ */
168
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
169
+ throw new BadDataException("Service account JSON must be a JSON object.");
170
+ }
171
+ /*
172
+ * Read before coercing: String({}) is "[object Object]", which is
173
+ * non-empty and sails through every check here only to fail an hour
174
+ * later inside jwt.sign, on the cron, where nobody is looking.
175
+ */
176
+ if (typeof parsed["client_email"] !== "string" ||
177
+ typeof parsed["private_key"] !== "string") {
178
+ throw new BadDataException("Service account JSON must contain client_email and private_key as strings.");
179
+ }
40
180
  const clientEmail = String(parsed["client_email"] || "");
41
181
  const privateKey = String(parsed["private_key"] || "");
42
182
  const tokenUri = String(parsed["token_uri"] || "https://oauth2.googleapis.com/token");
43
183
  if (!clientEmail || !privateKey) {
44
184
  throw new BadDataException("Service account JSON must contain client_email and private_key.");
45
185
  }
186
+ GoogleSecOpsClient.validateTokenUri(tokenUri);
187
+ GoogleSecOpsClient.validatePrivateKey(privateKey);
46
188
  return { clientEmail, privateKey, tokenUri };
47
189
  }
48
190
  getApiBaseUrl() {
@@ -56,7 +198,7 @@ export default class GoogleSecOpsClient {
56
198
  TOKEN_EXPIRY_SLACK_IN_SECONDS * 1000) {
57
199
  return this.cachedAccessToken;
58
200
  }
59
- const issuedAtInSeconds = Math.floor(nowInMs / 1000);
201
+ const issuedAtInSeconds = Math.floor(nowInMs / 1000) - TOKEN_CLOCK_SKEW_IN_SECONDS;
60
202
  const assertion = jwt.sign({
61
203
  iss: this.credentials.clientEmail,
62
204
  scope: CHRONICLE_SCOPE,
@@ -68,61 +210,102 @@ export default class GoogleSecOpsClient {
68
210
  grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
69
211
  assertion: assertion,
70
212
  }).toString();
71
- const response = await this.fetchImplementation(this.credentials.tokenUri, {
213
+ const response = await this.fetchWithTimeout(this.credentials.tokenUri, {
72
214
  method: "POST",
73
215
  headers: {
74
216
  "Content-Type": "application/x-www-form-urlencoded",
75
217
  },
76
218
  body: body,
77
- });
219
+ }, "token exchange");
78
220
  const responseText = await response.text();
79
221
  if (!response.ok) {
80
- throw new APIException(`Google token exchange failed (HTTP ${response.status}): ${responseText.slice(0, 500)}`);
222
+ throw new APIException(`Google token exchange failed (HTTP ${response.status}): ${responseText.slice(0, BODY_ECHO_LIMIT)}`);
81
223
  }
82
- const tokenResponse = JSON.parse(responseText);
224
+ /*
225
+ * Symmetrical with the alerts parse below: a proxy answering 200 with
226
+ * an HTML page would otherwise throw a raw SyntaxError, which is
227
+ * neither APIException nor BadDataException and so escapes the
228
+ * failure taxonomy the operator guidance is written against.
229
+ */
230
+ const tokenResponse = GoogleSecOpsClient.parseTokenResponse(responseText);
83
231
  const accessToken = String(tokenResponse["access_token"] || "");
84
- const expiresInSeconds = Number(tokenResponse["expires_in"] || TOKEN_LIFETIME_IN_SECONDS);
232
+ const expiresInSeconds = GoogleSecOpsClient.readExpiresInSeconds(tokenResponse["expires_in"]);
85
233
  if (!accessToken) {
86
234
  throw new APIException("Google token exchange returned no access_token.");
87
235
  }
88
- this.cachedAccessToken = accessToken;
89
- this.cachedAccessTokenExpiresAtInMs = nowInMs + expiresInSeconds * 1000;
236
+ /*
237
+ * A zero, negative or unparseable expires_in used to become either an
238
+ * hour of caching a dead token or a NaN that made the cache test
239
+ * permanently false. Neither is worth guessing at: use the token once
240
+ * and mint a fresh one next time.
241
+ */
242
+ if (expiresInSeconds > 0) {
243
+ this.cachedAccessToken = accessToken;
244
+ this.cachedAccessTokenExpiresAtInMs = nowInMs + expiresInSeconds * 1000;
245
+ }
246
+ else {
247
+ this.cachedAccessToken = null;
248
+ this.cachedAccessTokenExpiresAtInMs = 0;
249
+ }
90
250
  return accessToken;
91
251
  }
92
252
  /*
93
253
  * Fetch detection alerts created in a time window, via the Chronicle
94
- * v1alpha legacy alerts view. Response shapes vary across tenant
95
- * versions, so parsing is deliberately tolerant: `alerts`, `detections`,
96
- * or a bare array all work; anything else returns [] rather than
97
- * guessing.
254
+ * v1alpha legacy alerts view. The endpoint has no pagination of any
255
+ * kind, so maxReturnedAlerts is a real ceiling rather than a page size —
256
+ * truncation is reported through the result's flags, never swallowed.
98
257
  */
99
258
  async fetchDetectionAlerts(data) {
100
- const accessToken = await this.getAccessToken();
259
+ let accessToken = await this.getAccessToken();
260
+ const maxReturnedAlerts = data.maxAlerts || DEFAULT_MAX_ALERTS;
261
+ /*
262
+ * `alertListOptions.maxReturnedAlerts` is the flattened field path the
263
+ * HTTP transcoder binds AlertListOptions.max_returned_alerts from. No
264
+ * Google page prints this literal for this method — it is derived from
265
+ * google.api.HttpRule transcoding and corroborated by two independent
266
+ * shipping clients (Google's own secops-wrapper SDK and Fortinet's
267
+ * certified FortiSOAR connector), not verified verbatim in the docs.
268
+ * It is safe to send anyway because the parameter is optional and the
269
+ * failure mode is loud: a wrong name 400s with `Unknown name`, exactly
270
+ * like the `pageSize` this replaced, and never fails silently.
271
+ */
101
272
  const params = new URLSearchParams({
102
273
  "timeRange.startTime": data.startTime.toISOString(),
103
274
  "timeRange.endTime": data.endTime.toISOString(),
104
- pageSize: String(data.maxAlerts || DEFAULT_MAX_ALERTS),
105
- });
106
- const url = `${this.getApiBaseUrl()}/legacy:legacyFetchAlertsView?${params.toString()}`;
107
- const response = await this.fetchImplementation(url, {
108
- method: "GET",
109
- headers: {
110
- Authorization: `Bearer ${accessToken}`,
111
- },
275
+ "alertListOptions.maxReturnedAlerts": String(maxReturnedAlerts),
112
276
  });
113
- const responseText = await response.text();
114
- if (!response.ok) {
115
- throw new APIException(`Google SecOps alerts fetch failed (HTTP ${response.status}): ${responseText.slice(0, 500)}`);
277
+ if (SNAPSHOT_QUERY) {
278
+ params.set("snapshotQuery", SNAPSHOT_QUERY);
116
279
  }
117
- let parsed;
118
- try {
119
- parsed = JSON.parse(responseText);
280
+ const url = `${this.getApiBaseUrl()}/legacy:legacyFetchAlertsView?${params.toString()}`;
281
+ let response = await this.requestAlerts(url, accessToken);
282
+ let responseText = await response.text();
283
+ /*
284
+ * A key revoked mid-lifetime leaves a cached token Google now refuses,
285
+ * and every poll until its stated expiry fails against it. One retry
286
+ * on a fresh token distinguishes "the token went stale" from "the
287
+ * credential is genuinely rejected", which is what the operator needs
288
+ * to read off lastError.
289
+ */
290
+ if (response.status === 401) {
291
+ this.clearCachedAccessToken();
292
+ accessToken = await this.getAccessToken();
293
+ response = await this.requestAlerts(url, accessToken);
294
+ responseText = await response.text();
120
295
  }
121
- catch (_a) {
122
- throw new APIException("Google SecOps alerts fetch returned a non-JSON body.");
296
+ if (!response.ok) {
297
+ throw new APIException(`Google SecOps alerts fetch failed (HTTP ${response.status}): ${responseText.slice(0, BODY_ECHO_LIMIT)}` +
298
+ GoogleSecOpsClient.describeHttpFailure(response.status, responseText));
123
299
  }
124
- return GoogleSecOpsClient.extractAlerts(parsed);
300
+ return GoogleSecOpsClient.parseAlertsBody(responseText, maxReturnedAlerts);
125
301
  }
302
+ /*
303
+ * The legacy top-level shapes, kept as a fallback so a tenant already
304
+ * being served by them keeps working. The recognition gate in
305
+ * parseAlertsBody runs ahead of this, so returning [] here means "this
306
+ * recognized chunk carried nothing", never "I did not understand the
307
+ * body".
308
+ */
126
309
  static extractAlerts(payload) {
127
310
  if (Array.isArray(payload)) {
128
311
  return payload.filter((item) => {
@@ -133,13 +316,571 @@ export default class GoogleSecOpsClient {
133
316
  return [];
134
317
  }
135
318
  const asObject = payload;
319
+ const collected = [];
320
+ /*
321
+ * Every array-valued key, not the first one found: `{alerts: [],
322
+ * detections: [...]}` used to return [] and drop the detections,
323
+ * because an empty array is still an array.
324
+ */
136
325
  for (const key of ["alerts", "detections"]) {
137
326
  const nested = asObject[key];
138
327
  if (Array.isArray(nested)) {
139
- return GoogleSecOpsClient.extractAlerts(nested);
328
+ for (const item of GoogleSecOpsClient.extractAlerts(nested)) {
329
+ collected.push(item);
330
+ }
331
+ }
332
+ }
333
+ return collected;
334
+ }
335
+ /*
336
+ * ---------------------------------------------------------------------
337
+ * Helpers. Everything below is deliberately declared after extractAlerts:
338
+ * SecurityEventsConnectorGuidanceAccuracy reads this file's method
339
+ * bodies by slicing between declarations, so a helper placed higher up
340
+ * would be counted as part of fetchDetectionAlerts.
341
+ * ---------------------------------------------------------------------
342
+ */
343
+ clearCachedAccessToken() {
344
+ this.cachedAccessToken = null;
345
+ this.cachedAccessTokenExpiresAtInMs = 0;
346
+ }
347
+ async requestAlerts(url, accessToken) {
348
+ /*
349
+ * Accept is not a formality here: leaving content negotiation to the
350
+ * server default is what turns a proxy in the path into the
351
+ * "non-JSON body" failure below.
352
+ */
353
+ return this.fetchWithTimeout(url, {
354
+ method: "GET",
355
+ headers: {
356
+ Authorization: `Bearer ${accessToken}`,
357
+ Accept: "application/json",
358
+ },
359
+ }, "alerts fetch");
360
+ }
361
+ async fetchWithTimeout(url, init, stepLabel) {
362
+ const controller = new AbortController();
363
+ const timer = setTimeout(() => {
364
+ controller.abort();
365
+ }, REQUEST_TIMEOUT_IN_MS);
366
+ try {
367
+ return await this.fetchImplementation(url, Object.assign(Object.assign({}, init), { signal: controller.signal }));
368
+ }
369
+ catch (error) {
370
+ if (controller.signal.aborted) {
371
+ throw new APIException(`Google SecOps ${stepLabel} timed out after ${REQUEST_TIMEOUT_IN_SECONDS} seconds with no response.`);
372
+ }
373
+ throw error;
374
+ }
375
+ finally {
376
+ clearTimeout(timer);
377
+ }
378
+ }
379
+ static validateTokenUri(tokenUri) {
380
+ let parsed;
381
+ try {
382
+ parsed = new URL(tokenUri);
383
+ }
384
+ catch (_a) {
385
+ throw new BadDataException("Service account token_uri must be an absolute https URL.");
386
+ }
387
+ const isGoogleHost = TOKEN_URI_HOSTS.indexOf(parsed.hostname) !== -1 ||
388
+ parsed.hostname.endsWith(TOKEN_URI_HOST_SUFFIX);
389
+ if (parsed.protocol !== "https:" || !isGoogleHost || parsed.username) {
390
+ throw new BadDataException("Service account token_uri must be an https URL on a Google host such as https://oauth2.googleapis.com/token.");
391
+ }
392
+ }
393
+ static validatePrivateKey(privateKey) {
394
+ /*
395
+ * A double-escaped or truncated key saves cleanly today and fails an
396
+ * hour later inside jwt.sign as an unwrapped OpenSSL decoder error, on
397
+ * the cron — which is the exact class of failure save-time validation
398
+ * exists to prevent.
399
+ */
400
+ try {
401
+ createPrivateKey(privateKey);
402
+ }
403
+ catch (_a) {
404
+ throw new BadDataException("Service account private_key is not a readable PEM private key. Check that newlines are real newlines and the key is not encrypted.");
405
+ }
406
+ }
407
+ static validateRegionMatchesInstance(region, instanceResourceName) {
408
+ const match = instanceResourceName.match(INSTANCE_LOCATION_REGEX);
409
+ const location = match && match[1] ? match[1] : "";
410
+ if (!location || location === region) {
411
+ return;
412
+ }
413
+ if (EU_REGION_ALIASES.indexOf(region) !== -1 &&
414
+ EU_REGION_ALIASES.indexOf(location) !== -1) {
415
+ return;
416
+ }
417
+ throw new BadDataException("Region must match the locations segment of the instance resource name.");
418
+ }
419
+ static encodeInstanceResourceName(name) {
420
+ return name
421
+ .split("/")
422
+ .map((segment) => {
423
+ return encodeURIComponent(segment);
424
+ })
425
+ .join("/");
426
+ }
427
+ static parseTokenResponse(responseText) {
428
+ let parsed;
429
+ try {
430
+ parsed = JSON.parse(responseText);
431
+ }
432
+ catch (_a) {
433
+ throw new APIException("Google token exchange returned a non-JSON body.");
434
+ }
435
+ if (!GoogleSecOpsClient.isJsonObject(parsed)) {
436
+ throw new APIException("Google token exchange returned a body that is not a JSON object.");
437
+ }
438
+ return parsed;
439
+ }
440
+ static readExpiresInSeconds(value) {
441
+ if (value === undefined || value === null || value === "") {
442
+ return TOKEN_LIFETIME_IN_SECONDS;
443
+ }
444
+ const seconds = Number(value);
445
+ if (!Number.isFinite(seconds) || seconds <= 0) {
446
+ logger.warn(`GoogleSecOpsClient: token endpoint returned an unusable expires_in (${String(value)}); not caching this access token.`);
447
+ return 0;
448
+ }
449
+ return seconds;
450
+ }
451
+ /*
452
+ * The streaming envelope. Returns what the window actually contained, or
453
+ * throws — the one thing it must never do is report zero alerts for a
454
+ * body it did not recognize, because the poller reads that as a healthy
455
+ * quiet window and advances its cursor past whatever it failed to read.
456
+ */
457
+ static parseAlertsBody(bodyText, maxReturnedAlerts = DEFAULT_MAX_ALERTS) {
458
+ var _a, _b, _c;
459
+ const text = (bodyText || "").trim();
460
+ if (!text) {
461
+ throw new APIException("Google SecOps alerts fetch returned an empty body.");
462
+ }
463
+ const root = GoogleSecOpsClient.parseAlertsJson(text);
464
+ const chunks = GoogleSecOpsClient.toChunkArray(root, text);
465
+ /*
466
+ * Before anything is accumulated. A terminal google.rpc.Status can
467
+ * legitimately follow good data — [{alerts},{alerts},{error}] is a
468
+ * mid-stream failure — so a scan that stopped at the first element, or
469
+ * trusted the alerts it had already seen, would mask it.
470
+ */
471
+ GoogleSecOpsClient.throwOnStreamError(chunks);
472
+ GoogleSecOpsClient.throwOnUnrecognizedChunks(chunks, text);
473
+ const alerts = GoogleSecOpsClient.accumulateAlerts(chunks);
474
+ const complete = GoogleSecOpsClient.readLastBoolean(chunks, "complete") === true;
475
+ const progress = (_a = GoogleSecOpsClient.readLastNumber(chunks, "progress")) !== null && _a !== void 0 ? _a : 0;
476
+ const truncatedByCount = GoogleSecOpsClient.readLastBoolean(chunks, "tooManyAlerts") === true;
477
+ const truncatedByBytes = GoogleSecOpsClient.readLastBoolean(chunks, "memoryLimitExceeded") ===
478
+ true;
479
+ const baselineAlertsCount = (_b = GoogleSecOpsClient.readLastNumber(chunks, "baselineAlertsCount")) !== null && _b !== void 0 ? _b : 0;
480
+ const filteredAlertsCount = (_c = GoogleSecOpsClient.readLastNumber(chunks, "filteredAlertsCount")) !== null && _c !== void 0 ? _c : 0;
481
+ GoogleSecOpsClient.throwOnInBandValidationErrors(chunks);
482
+ if (truncatedByCount) {
483
+ logger.warn("GoogleSecOpsClient: Chronicle set tooManyAlerts — the window matched more alerts than it will return, and this endpoint has no pagination. Narrow the poll window.");
484
+ }
485
+ if (truncatedByBytes) {
486
+ logger.warn("GoogleSecOpsClient: Chronicle set memoryLimitExceeded — the result was truncated server side. Narrow the poll window.");
487
+ }
488
+ if (!complete) {
489
+ /*
490
+ * Warn rather than re-issue the whole GET in a loop the way Google's
491
+ * SDK does: the poller runs every minute over a 15-minute window
492
+ * with a minute of overlap, so a partial window is re-covered on the
493
+ * next tick, and a blocking retry inside a strictly sequential
494
+ * connection loop would let one slow tenant starve every other one.
495
+ */
496
+ logger.warn("GoogleSecOpsClient: the alerts stream ended without complete=true; this window may be partial and will be re-covered by the next poll.");
497
+ }
498
+ if (alerts.length > maxReturnedAlerts) {
499
+ /*
500
+ * Whether chunk.alerts is cumulative or incremental across chunks is
501
+ * the one part of this contract no Google page states. The union
502
+ * below is correct either way, but more alerts than the ceiling we
503
+ * asked for can only mean we appended across chunks that were
504
+ * restating the same top-N — an observable answer to an otherwise
505
+ * unanswerable doc question.
506
+ */
507
+ logger.error(`GoogleSecOpsClient: accumulated ${alerts.length} alerts for a ceiling of ${maxReturnedAlerts}. Chunk alerts are cumulative and the union is over-counting; the dedupe key is not identifying them.`);
508
+ }
509
+ return {
510
+ alerts: alerts,
511
+ complete: complete,
512
+ progress: progress,
513
+ truncatedByCount: truncatedByCount,
514
+ truncatedByBytes: truncatedByBytes,
515
+ baselineAlertsCount: baselineAlertsCount,
516
+ filteredAlertsCount: filteredAlertsCount,
517
+ chunkCount: chunks.length,
518
+ };
519
+ }
520
+ static parseAlertsJson(text) {
521
+ try {
522
+ return JSON.parse(text);
523
+ }
524
+ catch (_a) {
525
+ // Fall through to the repair pass.
526
+ }
527
+ try {
528
+ return JSON.parse(GoogleSecOpsClient.repairJsonBody(text));
529
+ }
530
+ catch (_b) {
531
+ throw new APIException("Google SecOps alerts fetch returned a non-JSON body.");
532
+ }
533
+ }
534
+ /*
535
+ * Bounded, and only after a strict parse has already failed. Google's own
536
+ * SDK ships an equivalent fixer, which only makes sense because malformed
537
+ * bodies were observed in the wild; it does not license repairing
538
+ * arbitrary text, so nothing here invents structure that was not there.
539
+ */
540
+ static repairJsonBody(text) {
541
+ let repaired = text.replace(/\}\s*\n\s*\{/g, "},\n{");
542
+ repaired = repaired.replace(/,\s*([\]}])/g, "$1");
543
+ if (repaired.startsWith("{") && repaired.endsWith("}")) {
544
+ repaired = `[${repaired}]`;
545
+ }
546
+ return repaired;
547
+ }
548
+ static toChunkArray(root, text) {
549
+ if (Array.isArray(root)) {
550
+ return root.filter((item) => {
551
+ return GoogleSecOpsClient.isJsonObject(item);
552
+ });
553
+ }
554
+ // A unary body instead of the stream envelope is tolerated.
555
+ if (GoogleSecOpsClient.isJsonObject(root)) {
556
+ return [root];
557
+ }
558
+ throw new APIException(`Google SecOps alerts fetch returned an unexpected response root: ${text.slice(0, BODY_ECHO_LIMIT)}`);
559
+ }
560
+ static throwOnStreamError(chunks) {
561
+ for (const chunk of chunks) {
562
+ const error = chunk["error"];
563
+ if (GoogleSecOpsClient.isJsonObject(error)) {
564
+ throw new APIException(`Google SecOps alerts fetch returned an error in the response stream: ${GoogleSecOpsClient.summarizeErrorObject(error)}`);
565
+ }
566
+ }
567
+ }
568
+ static throwOnUnrecognizedChunks(chunks, text) {
569
+ const recognized = chunks.filter((chunk) => {
570
+ return RECOGNIZED_CHUNK_FIELDS.some((field) => {
571
+ return Object.prototype.hasOwnProperty.call(chunk, field);
572
+ });
573
+ });
574
+ if (recognized.length === 0) {
575
+ throw new APIException(`Google SecOps alerts fetch returned an unrecognized response shape: ${text.slice(0, BODY_ECHO_LIMIT)}`);
576
+ }
577
+ }
578
+ /*
579
+ * Dedupe-by-id union, which is correct whether chunk.alerts is cumulative
580
+ * (later chunks refresh earlier entries) or incremental (entries
581
+ * accumulate). A blind push would double-count under the first reading; a
582
+ * blind replace would lose data under the second.
583
+ */
584
+ static accumulateAlerts(chunks) {
585
+ const seen = new Map();
586
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
587
+ const chunk = chunks[chunkIndex];
588
+ const chunkAlerts = GoogleSecOpsClient.alertsInChunk(chunk);
589
+ for (let position = 0; position < chunkAlerts.length; position++) {
590
+ const alert = chunkAlerts[position];
591
+ const id = alert["id"];
592
+ const key = typeof id === "string" && id ? id : `__idx:${chunkIndex}:${position}`;
593
+ seen.set(key, alert);
594
+ }
595
+ }
596
+ return Array.from(seen.values());
597
+ }
598
+ static alertsInChunk(chunk) {
599
+ const collected = [];
600
+ const alertList = chunk["alerts"];
601
+ /*
602
+ * FetchAlertsViewResponse.alerts is an AlertList message whose single
603
+ * field is also called `alerts`. Both levels are omitted when empty.
604
+ */
605
+ if (GoogleSecOpsClient.isJsonObject(alertList)) {
606
+ const inner = alertList["alerts"];
607
+ if (Array.isArray(inner)) {
608
+ for (const item of GoogleSecOpsClient.extractAlerts(inner)) {
609
+ collected.push(item);
610
+ }
611
+ }
612
+ }
613
+ for (const item of GoogleSecOpsClient.extractAlerts(chunk)) {
614
+ collected.push(item);
615
+ }
616
+ return collected;
617
+ }
618
+ /*
619
+ * complete, progress and the counts are proto3 scalars: a chunk where
620
+ * complete is false and progress is 0 omits both keys entirely, so
621
+ * "absent" must never be read as "present and false" — hence last chunk
622
+ * that CARRIES the key, not last chunk.
623
+ */
624
+ static readLastBoolean(chunks, field) {
625
+ let value = null;
626
+ for (const chunk of chunks) {
627
+ if (typeof chunk[field] === "boolean") {
628
+ value = chunk[field];
629
+ }
630
+ }
631
+ return value;
632
+ }
633
+ static readLastNumber(chunks, field) {
634
+ let value = null;
635
+ for (const chunk of chunks) {
636
+ const read = GoogleSecOpsClient.readNumber(chunk[field]);
637
+ if (read !== null) {
638
+ value = read;
639
+ }
640
+ }
641
+ return value;
642
+ }
643
+ static readNumber(value) {
644
+ if (typeof value === "number" && Number.isFinite(value)) {
645
+ return value;
646
+ }
647
+ // proto3 renders int64 as a JSON string, so counts can arrive quoted.
648
+ if (typeof value === "string" && value.trim() !== "") {
649
+ const parsed = Number(value);
650
+ if (Number.isFinite(parsed)) {
651
+ return parsed;
652
+ }
653
+ }
654
+ return null;
655
+ }
656
+ /*
657
+ * A malformed query comes back as HTTP 200 with the complaint in the
658
+ * body, so checking response.ok alone reports it as a successful poll
659
+ * that found nothing.
660
+ */
661
+ static throwOnInBandValidationErrors(chunks) {
662
+ const queryErrors = GoogleSecOpsClient.collectErrorTexts(chunks, "queryValidationErrors");
663
+ const runtimeErrors = GoogleSecOpsClient.collectErrorTexts(chunks, "runtimeErrors");
664
+ const invalidSnapshotQuery = GoogleSecOpsClient.readLastBoolean(chunks, "validSnapshotQuery") ===
665
+ false;
666
+ const invalidBaselineQuery = GoogleSecOpsClient.readLastBoolean(chunks, "validBaselineQuery") ===
667
+ false;
668
+ if (!invalidSnapshotQuery &&
669
+ !invalidBaselineQuery &&
670
+ queryErrors.length === 0 &&
671
+ runtimeErrors.length === 0) {
672
+ return;
673
+ }
674
+ const reasons = [];
675
+ if (invalidSnapshotQuery) {
676
+ reasons.push("validSnapshotQuery=false");
677
+ }
678
+ if (invalidBaselineQuery) {
679
+ reasons.push("validBaselineQuery=false");
680
+ }
681
+ for (const text of queryErrors) {
682
+ reasons.push(text);
683
+ }
684
+ for (const text of runtimeErrors) {
685
+ reasons.push(text);
686
+ }
687
+ throw new APIException(`Google SecOps alerts query was rejected by Chronicle on an HTTP 200: ${reasons.join("; ").slice(0, BODY_ECHO_LIMIT)}`);
688
+ }
689
+ static collectErrorTexts(chunks, field) {
690
+ /*
691
+ * Deduped rather than concatenated, for the same reason the alerts are:
692
+ * a cumulative chunk restates the whole list every time.
693
+ */
694
+ const seen = new Map();
695
+ for (const chunk of chunks) {
696
+ const entries = chunk[field];
697
+ if (!Array.isArray(entries)) {
698
+ continue;
699
+ }
700
+ for (const entry of entries) {
701
+ const text = GoogleSecOpsClient.errorTextOf(entry);
702
+ if (text) {
703
+ seen.set(text, text);
704
+ }
705
+ }
706
+ }
707
+ return Array.from(seen.values());
708
+ }
709
+ static errorTextOf(entry) {
710
+ if (typeof entry === "string") {
711
+ return entry;
712
+ }
713
+ if (GoogleSecOpsClient.isJsonObject(entry)) {
714
+ /*
715
+ * The reference page does not print ValidationError's field names, so
716
+ * the candidates below are a best effort; the JSON fallback keeps the
717
+ * operator-visible text honest when none of them match.
718
+ */
719
+ for (const key of ["errorText", "message", "error", "description"]) {
720
+ const value = entry[key];
721
+ if (typeof value === "string" && value) {
722
+ return value;
723
+ }
724
+ }
725
+ return JSON.stringify(entry);
726
+ }
727
+ return String(entry);
728
+ }
729
+ /*
730
+ * Actionable operator guidance appended behind the echoed body. The
731
+ * prefix, the status and the body slice ahead of it are the contract the
732
+ * integration doc and the in-product help are written against, so this
733
+ * only ever adds to the tail.
734
+ */
735
+ static describeHttpFailure(status, bodyText) {
736
+ const error = GoogleSecOpsClient.findErrorObject(bodyText);
737
+ const reason = GoogleSecOpsClient.errorInfoReason(error);
738
+ const message = error
739
+ ? String(error["message"] || "")
740
+ : bodyText.slice(0, BODY_ECHO_LIMIT);
741
+ const hint = reason
742
+ ? GoogleSecOpsClient.hintForReason(reason, error)
743
+ : GoogleSecOpsClient.hintForStatus(status, message, error);
744
+ if (!hint) {
745
+ return "";
746
+ }
747
+ return ` — ${hint}`;
748
+ }
749
+ static hintForReason(reason, error) {
750
+ if (reason === "CREDENTIALS_MISSING") {
751
+ return "No credential reached Google. The Authorization header was absent or unreadable.";
752
+ }
753
+ if (reason === "ACCESS_TOKEN_EXPIRED") {
754
+ return "The access token had expired; OneUptime mints a fresh one and retries once, so a repeat means the clock or the key is wrong.";
755
+ }
756
+ if (reason === "ACCESS_TOKEN_SCOPE_INSUFFICIENT") {
757
+ return "The token was minted without the Chronicle scope. Grant the service account https://www.googleapis.com/auth/cloud-platform or .../auth/chronicle.";
758
+ }
759
+ if (reason === "IAM_PERMISSION_DENIED") {
760
+ // ErrorInfo.metadata names the exact resource and permission; render both.
761
+ const metadata = GoogleSecOpsClient.errorInfoMetadata(error);
762
+ const resource = String((metadata === null || metadata === void 0 ? void 0 : metadata["resource"]) || "");
763
+ const permission = String((metadata === null || metadata === void 0 ? void 0 : metadata["permission"]) || "");
764
+ return `The service account lacks ${permission || "chronicle.legacies.legacyFetchAlertsView"} on ${resource || "the instance"}. Grant roles/chronicle.viewer (roles/chronicle.admin if Viewer is not enough on this tenant).`;
765
+ }
766
+ if (reason === "SERVICE_DISABLED") {
767
+ return "The Chronicle API is not enabled on the project the instance is bound to. Enable it in the Google Cloud console.";
768
+ }
769
+ if (reason === "RATE_LIMIT_EXCEEDED") {
770
+ const metadata = GoogleSecOpsClient.errorInfoMetadata(error);
771
+ const quotaMetric = String((metadata === null || metadata === void 0 ? void 0 : metadata["quota_metric"]) || "");
772
+ const quotaLimit = String((metadata === null || metadata === void 0 ? void 0 : metadata["quota_limit"]) || "");
773
+ return `Google is rate limiting this service account${quotaMetric ? ` on ${quotaMetric}` : ""}${quotaLimit ? ` (limit ${quotaLimit})` : ""}. Chronicle quota is per user per hour, so one service account shared across connections collides with itself.`;
774
+ }
775
+ return `Google reported ${reason}.`;
776
+ }
777
+ static hintForStatus(status, message, error) {
778
+ if (status === 400) {
779
+ /*
780
+ * AIP-193 requires a service-generated error to carry ErrorInfo, so a
781
+ * 400 that carries only BadRequest came from the HTTP transcoder —
782
+ * which means OneUptime's request shape is wrong, never the
783
+ * customer's credentials.
784
+ */
785
+ if (UNKNOWN_FIELD_PATTERN.test(message)) {
786
+ return "OneUptime sent a query parameter this endpoint does not accept. This is a OneUptime bug, not a credential or permission problem.";
787
+ }
788
+ if (MISSING_FIELD_PATTERN.test(message)) {
789
+ return "Chronicle rejected the request for a missing required field. This is a OneUptime bug, not a credential or permission problem.";
790
+ }
791
+ if (!error) {
792
+ return "Chronicle rejected the request shape before it reached the service. This is a OneUptime bug, not a credential or permission problem.";
793
+ }
794
+ return "";
795
+ }
796
+ if (status === 401) {
797
+ return "Google refused the access token. The service-account key may be revoked, or this host's clock may be skewed.";
798
+ }
799
+ if (status === 403) {
800
+ return "Either the instance resource name is wrong, or the service account lacks access to it. Grant roles/chronicle.viewer (roles/chronicle.admin if Viewer is not enough on this tenant) — permission is checked before existence, so a wrong instance name usually reads as 403 rather than 404.";
801
+ }
802
+ if (status === 404) {
803
+ return "The route did not resolve. Check the region prefix: googleapis.com is a DNS wildcard, so a mistyped region answers with a generic 404 rather than an API error.";
804
+ }
805
+ if (status === 429) {
806
+ return "RESOURCE_EXHAUSTED. Back off before the next poll; Chronicle quota is per user per hour, so one service account shared across connections collides with itself.";
807
+ }
808
+ return "";
809
+ }
810
+ /*
811
+ * us-chronicle.googleapis.com wraps its errors in the stream's array
812
+ * envelope, while an error rejected at the edge — bad auth, unknown route
813
+ * — comes back bare. Both shapes are read, and every element is scanned:
814
+ * the terminal google.rpc.Status is appended last, so body[0] is exactly
815
+ * the wrong place to look.
816
+ */
817
+ static findErrorObject(bodyText) {
818
+ const text = (bodyText || "").trim();
819
+ if (!text) {
820
+ return null;
821
+ }
822
+ let root;
823
+ try {
824
+ root = JSON.parse(text);
825
+ }
826
+ catch (_a) {
827
+ return null;
828
+ }
829
+ const elements = Array.isArray(root)
830
+ ? root
831
+ : [root];
832
+ for (const element of elements) {
833
+ if (!GoogleSecOpsClient.isJsonObject(element)) {
834
+ continue;
835
+ }
836
+ const error = element["error"];
837
+ if (GoogleSecOpsClient.isJsonObject(error)) {
838
+ return error;
140
839
  }
141
840
  }
142
- return [];
841
+ return null;
842
+ }
843
+ static errorInfoDetail(error) {
844
+ if (!error) {
845
+ return null;
846
+ }
847
+ const details = error["details"];
848
+ if (!Array.isArray(details)) {
849
+ return null;
850
+ }
851
+ for (const detail of details) {
852
+ if (!GoogleSecOpsClient.isJsonObject(detail)) {
853
+ continue;
854
+ }
855
+ if (String(detail["@type"] || "").endsWith("google.rpc.ErrorInfo") &&
856
+ detail["reason"]) {
857
+ return detail;
858
+ }
859
+ }
860
+ return null;
861
+ }
862
+ static errorInfoReason(error) {
863
+ const detail = GoogleSecOpsClient.errorInfoDetail(error);
864
+ return detail ? String(detail["reason"] || "") : "";
865
+ }
866
+ static errorInfoMetadata(error) {
867
+ const detail = GoogleSecOpsClient.errorInfoDetail(error);
868
+ if (!detail) {
869
+ return null;
870
+ }
871
+ const metadata = detail["metadata"];
872
+ return GoogleSecOpsClient.isJsonObject(metadata) ? metadata : null;
873
+ }
874
+ static summarizeErrorObject(error) {
875
+ const code = String(error["code"] || "");
876
+ const status = String(error["status"] || "");
877
+ const message = String(error["message"] || "");
878
+ return `${code ? `code ${code} ` : ""}${status ? `${status} ` : ""}${message}`
879
+ .trim()
880
+ .slice(0, BODY_ECHO_LIMIT);
881
+ }
882
+ static isJsonObject(value) {
883
+ return typeof value === "object" && value !== null && !Array.isArray(value);
143
884
  }
144
885
  }
145
886
  //# sourceMappingURL=GoogleSecOpsClient.js.map