@oneuptime/common 13.0.7 → 13.0.8

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 (463) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
  2. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
  3. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
  4. package/Server/API/AIBillingAPI.ts +1 -0
  5. package/Server/API/AIInsightAPI.ts +1 -4
  6. package/Server/API/AIInvestigationAPI.ts +1 -4
  7. package/Server/API/AIReadinessAPI.ts +4 -5
  8. package/Server/API/AlertAPI.ts +1 -0
  9. package/Server/API/AutoRemediationAPI.ts +1 -4
  10. package/Server/API/BaseAPI.ts +13 -0
  11. package/Server/API/BaseAnalyticsAPI.ts +80 -9
  12. package/Server/API/BillingAPI.ts +2 -0
  13. package/Server/API/BillingInvoiceAPI.ts +1 -0
  14. package/Server/API/BillingPaymentMethodAPI.ts +2 -0
  15. package/Server/API/CodeFixRunAPI.ts +1 -3
  16. package/Server/API/CommonAPI.ts +68 -4
  17. package/Server/API/DashboardAPI.ts +12 -12
  18. package/Server/API/GlobalConfigAPI.ts +17 -0
  19. package/Server/API/IncidentAPI.ts +2 -0
  20. package/Server/API/IncidentEpisodeAPI.ts +1 -0
  21. package/Server/API/NotificationAPI.ts +1 -0
  22. package/Server/API/OnCallCalendarAPI.ts +4 -5
  23. package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
  24. package/Server/API/OnCallReadinessAPI.ts +4 -1
  25. package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
  26. package/Server/API/ProjectAPI.ts +1 -0
  27. package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
  28. package/Server/API/StatusPageAPI.ts +161 -32
  29. package/Server/API/TeamMemberAPI.ts +3 -0
  30. package/Server/API/UserCallAPI.ts +2 -0
  31. package/Server/API/UserEmailAPI.ts +2 -0
  32. package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
  33. package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
  34. package/Server/API/UserSlackAPI.ts +1 -0
  35. package/Server/API/UserSmsAPI.ts +2 -0
  36. package/Server/API/UserTotpAuthAPI.ts +1 -0
  37. package/Server/API/UserWebAuthnAPI.ts +2 -0
  38. package/Server/API/UserWebhookAPI.ts +1 -0
  39. package/Server/API/UserWhatsAppAPI.ts +2 -0
  40. package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
  41. package/Server/Middleware/UserAuthorization.ts +79 -5
  42. package/Server/Services/ApiKeyPermissionService.ts +101 -4
  43. package/Server/Services/DatabaseService.ts +245 -0
  44. package/Server/Services/DetectionRuleService.ts +5 -4
  45. package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
  46. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
  47. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
  48. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
  49. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
  50. package/Server/Services/PayAsYouGoBillingService.ts +23 -28
  51. package/Server/Services/ProjectService.ts +18 -0
  52. package/Server/Services/RumSessionPinService.ts +274 -22
  53. package/Server/Services/TeamPermissionService.ts +120 -26
  54. package/Server/Services/ThreatIntelFeedService.ts +6 -3
  55. package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
  56. package/Server/Types/Database/Permissions/Index.ts +75 -11
  57. package/Server/Types/Database/Permissions/TablePermission.ts +24 -28
  58. package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
  59. package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
  60. package/Server/Utils/MasterAdminPageAccess.ts +61 -0
  61. package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
  62. package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
  63. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
  64. package/Server/Utils/Realtime.ts +163 -45
  65. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
  66. package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
  67. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
  68. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
  69. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
  70. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
  71. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
  72. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
  73. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
  74. package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
  75. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
  76. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
  77. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
  78. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
  79. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
  80. package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
  81. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
  82. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
  83. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
  84. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
  85. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
  86. package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
  87. package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
  88. package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
  89. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +95 -68
  90. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
  91. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +28 -3
  92. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +761 -0
  93. package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
  94. package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
  95. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
  96. package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
  97. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +321 -0
  98. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
  99. package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
  100. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
  101. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
  102. package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
  103. package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
  104. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
  105. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
  106. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
  107. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
  108. package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
  109. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
  110. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
  111. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
  112. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
  113. package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
  114. package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
  115. package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
  116. package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
  117. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
  118. package/Tests/Server/API/GlobalConfigLicense.test.ts +140 -0
  119. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
  120. package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
  121. package/Tests/Server/API/OnCallReadinessAPI.test.ts +175 -17
  122. package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
  123. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
  124. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
  125. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
  126. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
  127. package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
  128. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
  129. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
  130. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
  131. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
  132. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
  133. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
  134. package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
  135. package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
  136. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
  137. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
  138. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
  139. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
  140. package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
  141. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
  142. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
  143. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
  144. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
  145. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
  146. package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
  147. package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
  148. package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
  149. package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
  150. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
  151. package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
  152. package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
  153. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
  154. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
  155. package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
  156. package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
  157. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
  158. package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
  159. package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
  160. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
  161. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
  162. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
  163. package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
  164. package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
  165. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
  166. package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
  167. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
  168. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
  169. package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
  170. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
  171. package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
  172. package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
  173. package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
  174. package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
  175. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
  176. package/Tests/Server/Utils/Realtime.test.ts +502 -0
  177. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
  178. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
  179. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
  180. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
  181. package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
  182. package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
  183. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
  184. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
  185. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
  186. package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
  187. package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
  188. package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
  189. package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
  190. package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
  191. package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
  192. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
  193. package/Tests/Types/Call/CallProvider.test.ts +528 -0
  194. package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
  195. package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
  196. package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
  197. package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
  198. package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
  199. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
  200. package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
  201. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +89 -1
  202. package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
  203. package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
  204. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
  205. package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
  206. package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
  207. package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
  208. package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
  209. package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
  210. package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
  211. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
  212. package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
  213. package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
  214. package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
  215. package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
  216. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  217. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  218. package/Tests/UI/Utils/Cookie.test.ts +440 -0
  219. package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
  220. package/Tests/UI/Utils/Navigation.test.ts +33 -0
  221. package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
  222. package/Tests/UI/Utils/Realtime.test.ts +759 -0
  223. package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
  224. package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
  225. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
  226. package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
  227. package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
  228. package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
  229. package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
  230. package/Types/Realtime/EventName.ts +11 -0
  231. package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
  232. package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
  233. package/Types/SecurityEvent/SigmaRule.ts +51 -0
  234. package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
  235. package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
  236. package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
  237. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  238. package/UI/Components/EditionLabel/EditionLabel.tsx +12 -0
  239. package/UI/Components/EventItem/EventItem.tsx +12 -1
  240. package/UI/Components/Forms/BasicForm.tsx +151 -126
  241. package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
  242. package/UI/Components/Forms/Fields/FormField.tsx +10 -4
  243. package/UI/Components/Forms/FormSummary.tsx +7 -0
  244. package/UI/Components/Forms/Types/Field.ts +10 -0
  245. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
  246. package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
  247. package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
  248. package/UI/Components/ModelTable/TableView.tsx +1 -1
  249. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
  250. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  251. package/UI/Utils/API/API.ts +173 -12
  252. package/UI/Utils/API/RequestOptions.ts +11 -0
  253. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
  254. package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
  255. package/UI/Utils/Navigation.ts +9 -0
  256. package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
  257. package/UI/Utils/Realtime.ts +176 -30
  258. package/Utils/API.ts +14 -5
  259. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
  260. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
  261. package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
  262. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
  263. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
  264. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
  265. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
  266. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
  267. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
  268. package/build/dist/Server/API/AIBillingAPI.js +1 -1
  269. package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
  270. package/build/dist/Server/API/AIInsightAPI.js +1 -4
  271. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  272. package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
  273. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  274. package/build/dist/Server/API/AIReadinessAPI.js +1 -3
  275. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
  276. package/build/dist/Server/API/AlertAPI.js +1 -1
  277. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  278. package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
  279. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  280. package/build/dist/Server/API/BaseAPI.js +12 -0
  281. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  282. package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
  283. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  284. package/build/dist/Server/API/BillingAPI.js +2 -2
  285. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  286. package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
  287. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  288. package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
  289. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  290. package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
  291. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  292. package/build/dist/Server/API/CommonAPI.js +50 -5
  293. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  294. package/build/dist/Server/API/DashboardAPI.js +12 -12
  295. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  296. package/build/dist/Server/API/GlobalConfigAPI.js +12 -0
  297. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  298. package/build/dist/Server/API/IncidentAPI.js +2 -2
  299. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  300. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  301. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  302. package/build/dist/Server/API/NotificationAPI.js +1 -1
  303. package/build/dist/Server/API/NotificationAPI.js.map +1 -1
  304. package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
  305. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
  306. package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
  307. package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
  308. package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
  309. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
  310. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
  311. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
  312. package/build/dist/Server/API/ProjectAPI.js +1 -1
  313. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  314. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  315. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  316. package/build/dist/Server/API/StatusPageAPI.js +125 -33
  317. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  318. package/build/dist/Server/API/TeamMemberAPI.js +3 -3
  319. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  320. package/build/dist/Server/API/UserCallAPI.js +2 -2
  321. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  322. package/build/dist/Server/API/UserEmailAPI.js +2 -2
  323. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  324. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
  325. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  326. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
  327. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
  328. package/build/dist/Server/API/UserSlackAPI.js +1 -1
  329. package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
  330. package/build/dist/Server/API/UserSmsAPI.js +2 -2
  331. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  332. package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
  333. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  334. package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
  335. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  336. package/build/dist/Server/API/UserWebhookAPI.js +1 -1
  337. package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
  338. package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
  339. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  340. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
  341. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  342. package/build/dist/Server/Middleware/UserAuthorization.js +64 -4
  343. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  344. package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
  345. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  346. package/build/dist/Server/Services/DatabaseService.js +171 -0
  347. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  348. package/build/dist/Server/Services/DetectionRuleService.js +4 -4
  349. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  350. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
  351. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
  352. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
  353. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  354. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
  355. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  356. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
  357. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  358. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
  359. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  360. package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
  361. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
  362. package/build/dist/Server/Services/ProjectService.js +17 -0
  363. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  364. package/build/dist/Server/Services/RumSessionPinService.js +180 -16
  365. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  366. package/build/dist/Server/Services/TeamPermissionService.js +89 -27
  367. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  368. package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
  369. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
  370. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
  371. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  372. package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
  373. package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
  374. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +14 -13
  375. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  376. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
  377. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
  378. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
  379. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
  380. package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
  381. package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
  382. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
  383. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  384. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
  385. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  386. package/build/dist/Server/Utils/Realtime.js +126 -37
  387. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  388. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
  389. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  390. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
  391. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
  392. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
  393. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  394. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
  395. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
  396. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
  397. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
  398. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
  399. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
  400. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
  401. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
  402. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
  403. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
  404. package/build/dist/Types/Realtime/EventName.js +10 -0
  405. package/build/dist/Types/Realtime/EventName.js.map +1 -1
  406. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
  407. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  408. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
  409. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
  410. package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
  411. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
  412. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
  413. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
  414. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
  415. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
  416. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
  417. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
  418. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
  419. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  420. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +11 -0
  421. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  422. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  423. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  424. package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
  425. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  426. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
  427. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
  428. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
  429. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  430. package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
  431. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  432. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
  433. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  434. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
  435. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
  436. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
  437. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  438. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  439. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  440. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
  441. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  442. package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
  443. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  444. package/build/dist/UI/Utils/API/API.js +143 -19
  445. package/build/dist/UI/Utils/API/API.js.map +1 -1
  446. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
  447. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  448. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
  449. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  450. package/build/dist/UI/Utils/Navigation.js +8 -0
  451. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  452. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
  453. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
  454. package/build/dist/UI/Utils/Realtime.js +120 -15
  455. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  456. package/build/dist/Utils/API.js +13 -5
  457. package/build/dist/Utils/API.js.map +1 -1
  458. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
  459. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  460. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
  461. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
  462. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
  463. package/package.json +1 -1
@@ -0,0 +1,928 @@
1
+ import { mockRouter } from "./Helpers";
2
+ import CommonAPI from "../../../Server/API/CommonAPI";
3
+ import OnCallReadinessAPI from "../../../Server/API/OnCallReadinessAPI";
4
+ import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
5
+ import OnCallDutyPolicyService from "../../../Server/Services/OnCallDutyPolicyService";
6
+ import OnCallReadinessService from "../../../Server/Services/OnCallReadinessService";
7
+ import OnCallSetupReminderService, {
8
+ SetupReminderOutcome,
9
+ SetupReminderResult,
10
+ SetupReminderUserResult,
11
+ } from "../../../Server/Services/OnCallSetupReminderService";
12
+ import ProjectService from "../../../Server/Services/ProjectService";
13
+ import TeamMemberService from "../../../Server/Services/TeamMemberService";
14
+ import CookieUtil from "../../../Server/Utils/Cookie";
15
+ import {
16
+ ExpressRequest,
17
+ ExpressResponse,
18
+ NextFunction,
19
+ OneUptimeRequest,
20
+ } from "../../../Server/Utils/Express";
21
+ import logger from "../../../Server/Utils/Logger";
22
+ import Response from "../../../Server/Utils/Response";
23
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
24
+ import Dictionary from "../../../Types/Dictionary";
25
+ import BadDataException from "../../../Types/Exception/BadDataException";
26
+ import Exception from "../../../Types/Exception/Exception";
27
+ import ExceptionCode from "../../../Types/Exception/ExceptionCode";
28
+ import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
29
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
30
+ import { JSONObject } from "../../../Types/JSON";
31
+ import ObjectID from "../../../Types/ObjectID";
32
+ import Permission, {
33
+ UserPermission,
34
+ UserTenantAccessPermission,
35
+ } from "../../../Types/Permission";
36
+ import UserType from "../../../Types/UserType";
37
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
38
+
39
+ /*
40
+ * The customer report, replayed at the server.
41
+ *
42
+ * An admin opened On-Call > Readiness, left the tab for more than fifteen
43
+ * minutes, came back and pressed "Send setup reminder". The dashboard answered
44
+ * "You are not authorized to access this project's data." They were still
45
+ * signed in - the refresh token was good for days - and they were an owner of
46
+ * the project.
47
+ *
48
+ * What happened on the wire: the access-token cookie's maxAge is the JWT's own
49
+ * fifteen-minute lifetime, so the browser had simply stopped sending it. The
50
+ * POST arrived with the `tenantid` header the dashboard always adds, the
51
+ * refresh-token cookie, and no access token. getUserMiddleware treats that as a
52
+ * Public request and calls next(); the route's member guard then saw no userId
53
+ * and refused with NotAuthorizedException - a 422. The browser client
54
+ * (Common/UI/Utils/API/API.ts) refreshes the session and replays the request on
55
+ * a 401 and on nothing else, so the 422 went straight to the screen.
56
+ *
57
+ * The fix is that "no credentials at all" is a 401 everywhere, decided before
58
+ * anything about the project is looked at. This file drives the readiness
59
+ * router - the customer's POST and its three GET siblings - the way that
60
+ * request actually arrives:
61
+ *
62
+ * - the REAL UserMiddleware.getUserMiddleware turns the cookie-less request
63
+ * into the Public request, instead of a fixture guessing at its shape;
64
+ * - the REAL CommonAPI.getDatabaseCommonInteractionProps turns that into
65
+ * props (only ProjectService's plan lookup is stubbed, because CI runs with
66
+ * billing enabled and the project does not exist);
67
+ * - the error the handler hands to next() is given to the REAL
68
+ * Response.sendErrorResponse, which applies the same "exception code is
69
+ * the HTTP status" rule as the app's error handler, so the assertions are
70
+ * on the status the browser would actually receive.
71
+ *
72
+ * Authenticated callers are built as getUserMiddleware leaves them after a
73
+ * successful token decode (userType, userAuthorization, tenant permissions),
74
+ * because running a real decode needs a signing secret and a database. What
75
+ * they pin is that the change did NOT move anyone else: a logged-in member
76
+ * without the reminder permission still gets the 422, and a privileged member
77
+ * still sends.
78
+ */
79
+
80
+ jest.mock("../../../Server/Utils/Express", () => {
81
+ return {
82
+ getRouter: () => {
83
+ return mockRouter;
84
+ },
85
+ };
86
+ });
87
+
88
+ const POLICY_ROUTE: string = "/on-call-readiness/policy/:policyId";
89
+ const PROJECT_ROUTE: string = "/on-call-readiness/project";
90
+ const USER_ROUTE: string = "/on-call-readiness/user/:userId";
91
+ const REMINDER_ROUTE: string = "/on-call-readiness/send-setup-reminder";
92
+
93
+ // The member / permission refusal: what the customer saw, and must not again.
94
+ const REFUSAL: string = "You are not authorized to access this project's data.";
95
+
96
+ type RouterFunction = (
97
+ req: ExpressRequest,
98
+ res: ExpressResponse,
99
+ next: NextFunction,
100
+ ) => void | Promise<void>;
101
+
102
+ // The slice of Helpers.ts's private Route type this file relies on.
103
+ interface RegisteredRoute {
104
+ method: string;
105
+ uri: string;
106
+ middlewares: Array<RouterFunction>;
107
+ handlerFunction: RouterFunction;
108
+ }
109
+
110
+ interface RouteCallResult {
111
+ // The exception the handler passed to next(), if any.
112
+ thrownToNext: unknown;
113
+ // Whether every middleware called next() and the handler itself ran.
114
+ reachedHandler: boolean;
115
+ // The HTTP status the client receives for a refusal, or undefined on success.
116
+ status: number | undefined;
117
+ // The body the client receives for a refusal.
118
+ errorBody: JSONObject | undefined;
119
+ }
120
+
121
+ let projectId: ObjectID;
122
+ let otherProjectId: ObjectID;
123
+ let callerUserId: ObjectID;
124
+ let subjectUserId: ObjectID;
125
+ let policyId: ObjectID;
126
+
127
+ let sendRemindersSpy: jest.SpyInstance;
128
+ let policyReadinessSpy: jest.SpyInstance;
129
+ let projectReadinessSpy: jest.SpyInstance;
130
+ let userReadinessSpy: jest.SpyInstance;
131
+ let clearCacheSpy: jest.SpyInstance;
132
+ let policyFindOneByIdSpy: jest.SpyInstance;
133
+ let teamMemberFindBySpy: jest.SpyInstance;
134
+ let sendJsonSpy: jest.SpyInstance;
135
+
136
+ function findRoute(method: string, uri: string): RegisteredRoute {
137
+ const route: RegisteredRoute | undefined = (
138
+ mockRouter.routes as unknown as Array<RegisteredRoute>
139
+ ).find((candidate: RegisteredRoute): boolean => {
140
+ return candidate.method === method && candidate.uri === uri;
141
+ });
142
+
143
+ if (!route) {
144
+ throw new Error(`No ${method} route registered for ${uri}`);
145
+ }
146
+
147
+ return route;
148
+ }
149
+
150
+ /*
151
+ * The request a browser sends once the access-token cookie has expired: the
152
+ * dashboard's `tenantid` header (ModelAPI.getCommonHeaders adds it to every
153
+ * call) and the refresh-token cookie, which is long-lived and so is still
154
+ * there. No access token and no Authorization header - which is the whole
155
+ * point.
156
+ */
157
+ function buildExpiredSessionRequest(data: {
158
+ tenantId?: ObjectID | undefined;
159
+ params?: Dictionary<string> | undefined;
160
+ query?: Dictionary<string> | undefined;
161
+ body?: JSONObject | undefined;
162
+ }): ExpressRequest {
163
+ const headers: Dictionary<string> = {};
164
+
165
+ if (data.tenantId) {
166
+ headers["tenantid"] = data.tenantId.toString();
167
+ }
168
+
169
+ const cookies: Dictionary<string> = {};
170
+ cookies[CookieUtil.getRefreshTokenKey()] = "still-valid-refresh-token";
171
+
172
+ return {
173
+ params: data.params || {},
174
+ query: data.query || {},
175
+ body: data.body || {},
176
+ headers: headers,
177
+ cookies: cookies,
178
+ } as unknown as ExpressRequest;
179
+ }
180
+
181
+ /*
182
+ * A logged-in caller as getUserMiddleware leaves the request after decoding a
183
+ * valid access token and loading the caller's permissions for the tenant.
184
+ *
185
+ * isBlockPermission is an explicit false on every entry: the permission reads
186
+ * filter on it strictly, so an undefined would silently drop the grant and turn
187
+ * the "privileged member" below into an unprivileged one.
188
+ */
189
+ function buildSignedInRequest(data: {
190
+ tenantId: ObjectID;
191
+ userId: ObjectID | undefined;
192
+ userType: UserType;
193
+ permissions: Array<Permission>;
194
+ body?: JSONObject | undefined;
195
+ }): ExpressRequest {
196
+ const userPermissions: Array<UserPermission> = data.permissions.map(
197
+ (permission: Permission): UserPermission => {
198
+ return {
199
+ _type: "UserPermission",
200
+ permission: permission,
201
+ labelIds: [],
202
+ isBlockPermission: false,
203
+ };
204
+ },
205
+ );
206
+
207
+ const tenantPermission: UserTenantAccessPermission = {
208
+ _type: "UserTenantAccessPermission",
209
+ projectId: data.tenantId,
210
+ permissions: userPermissions,
211
+ };
212
+
213
+ const permissionMap: Dictionary<UserTenantAccessPermission> = {};
214
+ permissionMap[data.tenantId.toString()] = tenantPermission;
215
+
216
+ return {
217
+ params: {},
218
+ query: {},
219
+ body: data.body || {},
220
+ headers: { tenantid: data.tenantId.toString() },
221
+ cookies: {},
222
+ tenantId: data.tenantId,
223
+ userType: data.userType,
224
+ userAuthorization: data.userId ? { userId: data.userId } : undefined,
225
+ userTenantAccessPermission: permissionMap,
226
+ } as unknown as ExpressRequest;
227
+ }
228
+
229
+ /*
230
+ * Runs a registered route's middleware chain and handler for real, then hands
231
+ * any error the handler raised to Response.sendErrorResponse - the app's error
232
+ * path, reduced to the one rule these assertions need: the exception's code is
233
+ * the HTTP status.
234
+ *
235
+ * `signedIn` requests skip the middleware: buildSignedInRequest already IS the
236
+ * middleware's output for a valid token, and re-running it without that token
237
+ * would overwrite the caller back to Public.
238
+ */
239
+ async function callRoute(data: {
240
+ method: string;
241
+ uri: string;
242
+ req: ExpressRequest;
243
+ signedIn?: boolean | undefined;
244
+ }): Promise<RouteCallResult> {
245
+ const route: RegisteredRoute = findRoute(data.method, data.uri);
246
+
247
+ const sent: { status: number | undefined; body: JSONObject | undefined } = {
248
+ status: undefined,
249
+ body: undefined,
250
+ };
251
+
252
+ const res: ExpressResponse = {
253
+ status: (code: number): ExpressResponse => {
254
+ sent.status = code;
255
+ return res;
256
+ },
257
+ send: (body: JSONObject): ExpressResponse => {
258
+ sent.body = body;
259
+ return res;
260
+ },
261
+ json: (body: JSONObject): ExpressResponse => {
262
+ sent.body = body;
263
+ return res;
264
+ },
265
+ setHeader: (): void => {
266
+ // not needed by the paths under test
267
+ },
268
+ } as unknown as ExpressResponse;
269
+
270
+ const middlewares: Array<RouterFunction> = data.signedIn
271
+ ? []
272
+ : route.middlewares;
273
+
274
+ for (const middleware of middlewares) {
275
+ const step: { calledNext: boolean; error: unknown } = {
276
+ calledNext: false,
277
+ error: undefined,
278
+ };
279
+
280
+ await middleware(data.req, res, ((error?: unknown): void => {
281
+ step.calledNext = true;
282
+ step.error = error;
283
+ }) as NextFunction);
284
+
285
+ if (!step.calledNext || step.error) {
286
+ // The middleware answered the request itself.
287
+ return {
288
+ thrownToNext: step.error,
289
+ reachedHandler: false,
290
+ status: sent.status,
291
+ errorBody: sent.body,
292
+ };
293
+ }
294
+ }
295
+
296
+ const outcome: { error: unknown } = { error: undefined };
297
+
298
+ await route.handlerFunction(data.req, res, ((error?: unknown): void => {
299
+ outcome.error = error;
300
+ }) as NextFunction);
301
+
302
+ if (outcome.error) {
303
+ Response.sendErrorResponse(data.req, res, outcome.error as Exception);
304
+ }
305
+
306
+ return {
307
+ thrownToNext: outcome.error,
308
+ reachedHandler: true,
309
+ status: sent.status,
310
+ errorBody: sent.body,
311
+ };
312
+ }
313
+
314
+ /*
315
+ * The refusal an expired session must get, checked at both ends: the exception
316
+ * the handler raised, and the status and message the browser receives.
317
+ */
318
+ function expectAskedToAuthenticate(result: RouteCallResult): void {
319
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthenticatedException);
320
+ expect(result.thrownToNext).not.toBeInstanceOf(NotAuthorizedException);
321
+ expect((result.thrownToNext as Exception).code).toBe(
322
+ ExceptionCode.NotAuthenticatedException,
323
+ );
324
+ expect(result.status).toBe(401);
325
+ expect(result.errorBody?.["message"]).toBe(
326
+ CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE,
327
+ );
328
+ expect(result.errorBody?.["message"]).not.toBe(REFUSAL);
329
+ }
330
+
331
+ function expectNoReadinessWork(): void {
332
+ expect(policyFindOneByIdSpy).not.toHaveBeenCalled();
333
+ expect(teamMemberFindBySpy).not.toHaveBeenCalled();
334
+ expect(policyReadinessSpy).not.toHaveBeenCalled();
335
+ expect(projectReadinessSpy).not.toHaveBeenCalled();
336
+ expect(userReadinessSpy).not.toHaveBeenCalled();
337
+ expect(clearCacheSpy).not.toHaveBeenCalled();
338
+ expect(sendRemindersSpy).not.toHaveBeenCalled();
339
+ expect(sendJsonSpy).not.toHaveBeenCalled();
340
+ }
341
+
342
+ function emptySummary(forProjectId: ObjectID): JSONObject {
343
+ return {
344
+ projectId: forProjectId,
345
+ readyCount: 0,
346
+ partiallyReadyCount: 0,
347
+ notReachableCount: 0,
348
+ isFallbackEnabled: true,
349
+ isTruncated: false,
350
+ users: [],
351
+ } as unknown as JSONObject;
352
+ }
353
+
354
+ beforeEach(() => {
355
+ jest.clearAllMocks();
356
+
357
+ projectId = ObjectID.generate();
358
+ otherProjectId = ObjectID.generate();
359
+ callerUserId = ObjectID.generate();
360
+ subjectUserId = ObjectID.generate();
361
+ policyId = ObjectID.generate();
362
+
363
+ // Both sources of 401 in this file log the refusal; keep the output clean.
364
+ jest.spyOn(logger, "error").mockImplementation((): void => {});
365
+
366
+ /*
367
+ * getUserMiddleware fires a lastActive write for any tenant header, and
368
+ * getDatabaseCommonInteractionProps resolves the plan when billing is on
369
+ * (it is in CI). Neither is under test, and both would reach the database.
370
+ */
371
+ jest.spyOn(ProjectService, "updateLastActive").mockResolvedValue(undefined);
372
+ jest
373
+ .spyOn(ProjectService, "getCurrentPlan")
374
+ .mockResolvedValue({ plan: null, isSubscriptionUnpaid: false });
375
+
376
+ sendJsonSpy = jest
377
+ .spyOn(Response, "sendJsonObjectResponse")
378
+ .mockImplementation((): void => {});
379
+
380
+ policyFindOneByIdSpy = jest
381
+ .spyOn(OnCallDutyPolicyService, "findOneById")
382
+ .mockResolvedValue({ id: policyId, projectId: projectId } as never);
383
+
384
+ teamMemberFindBySpy = jest
385
+ .spyOn(TeamMemberService, "findBy")
386
+ .mockResolvedValue([{ _id: ObjectID.generate().toString() }] as never);
387
+
388
+ policyReadinessSpy = jest
389
+ .spyOn(OnCallReadinessService, "getReadinessForPolicy")
390
+ .mockResolvedValue(emptySummary(projectId) as never);
391
+
392
+ projectReadinessSpy = jest
393
+ .spyOn(OnCallReadinessService, "getReadinessForProject")
394
+ .mockResolvedValue(emptySummary(projectId) as never);
395
+
396
+ userReadinessSpy = jest
397
+ .spyOn(OnCallReadinessService, "getReadinessForUser")
398
+ .mockRejectedValue(
399
+ new Error("not stubbed: no test here reads a single user") as never,
400
+ );
401
+
402
+ clearCacheSpy = jest.spyOn(OnCallReadinessService, "clearCache");
403
+
404
+ sendRemindersSpy = jest
405
+ .spyOn(OnCallSetupReminderService, "sendSetupReminders")
406
+ .mockImplementation((async (data: {
407
+ projectId: ObjectID;
408
+ userIds: Array<ObjectID>;
409
+ }): Promise<SetupReminderResult> => {
410
+ return {
411
+ projectId: data.projectId,
412
+ requestedCount: data.userIds.length,
413
+ sentCount: data.userIds.length,
414
+ skippedCount: 0,
415
+ failedCount: 0,
416
+ results: data.userIds.map(
417
+ (userId: ObjectID): SetupReminderUserResult => {
418
+ return {
419
+ userId: userId,
420
+ outcome: SetupReminderOutcome.Sent,
421
+ message: "Reminder sent to their account email address.",
422
+ };
423
+ },
424
+ ),
425
+ };
426
+ }) as never);
427
+ });
428
+
429
+ afterEach(() => {
430
+ jest.restoreAllMocks();
431
+ });
432
+
433
+ describe("the request an expired session actually sends", () => {
434
+ test("getUserMiddleware turns it into an anonymous Public request that still carries the tenant", async () => {
435
+ /*
436
+ * Pinned first because every other test in this file depends on it: the
437
+ * cookie-less request is let THROUGH the user middleware, not refused
438
+ * there, so the route's own guard is what answers it.
439
+ */
440
+ const req: ExpressRequest = buildExpiredSessionRequest({
441
+ tenantId: projectId,
442
+ body: { userIds: [subjectUserId.toString()] },
443
+ });
444
+
445
+ const next: jest.Mock = jest.fn();
446
+
447
+ await UserMiddleware.getUserMiddleware(
448
+ req,
449
+ {} as ExpressResponse,
450
+ next as unknown as NextFunction,
451
+ );
452
+
453
+ expect(next).toHaveBeenCalledTimes(1);
454
+ expect(next).toHaveBeenCalledWith();
455
+
456
+ const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
457
+
458
+ expect(oneUptimeRequest.userType).toBe(UserType.Public);
459
+ expect(oneUptimeRequest.tenantId?.toString()).toBe(projectId.toString());
460
+ expect(oneUptimeRequest.userAuthorization).toBeUndefined();
461
+ expect(UserMiddleware.isAnonymousRequest(oneUptimeRequest)).toBe(true);
462
+
463
+ const props: DatabaseCommonInteractionProps =
464
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
465
+
466
+ expect(props.userId).toBeUndefined();
467
+ expect(props.tenantId?.toString()).toBe(projectId.toString());
468
+ expect(CommonAPI.isAnonymous(props)).toBe(true);
469
+ });
470
+
471
+ test("the middleware's 401 and the guards' 401 read the same to the user", () => {
472
+ /*
473
+ * Both are "you need to log in again". If the refresh itself fails, this
474
+ * is the sentence the dashboard ends up showing, whichever layer refused.
475
+ */
476
+ expect(CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE).toBe(
477
+ UserMiddleware.AUTHENTICATION_REQUIRED_MESSAGE,
478
+ );
479
+ expect(CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE).toBe(
480
+ "Authentication required. Please log in to access this resource.",
481
+ );
482
+ });
483
+
484
+ test("the readiness routes are behind the user middleware only, so the handler's guard is what answers", () => {
485
+ for (const [method, uri] of [
486
+ ["GET", POLICY_ROUTE],
487
+ ["GET", PROJECT_ROUTE],
488
+ ["GET", USER_ROUTE],
489
+ ["POST", REMINDER_ROUTE],
490
+ ] as Array<[string, string]>) {
491
+ const route: RegisteredRoute = findRoute(method, uri);
492
+
493
+ expect(route.middlewares).toEqual([UserMiddleware.getUserMiddleware]);
494
+ }
495
+
496
+ // The module exports the router the routes were registered on.
497
+ expect(OnCallReadinessAPI).toBe(mockRouter);
498
+ });
499
+ });
500
+
501
+ describe("POST /on-call-readiness/send-setup-reminder with an expired session", () => {
502
+ test("is answered 401 - not the 422 'not authorized' the customer saw - and nothing is sent", async () => {
503
+ const result: RouteCallResult = await callRoute({
504
+ method: "POST",
505
+ uri: REMINDER_ROUTE,
506
+ req: buildExpiredSessionRequest({
507
+ tenantId: projectId,
508
+ body: { userIds: [subjectUserId.toString()] },
509
+ }),
510
+ });
511
+
512
+ expect(result.reachedHandler).toBe(true);
513
+ expectAskedToAuthenticate(result);
514
+ expect(result.status).not.toBe(ExceptionCode.NotAuthorizedException);
515
+ expectNoReadinessWork();
516
+ });
517
+
518
+ test("is 401 even without the tenant header - who you are is asked before which project", async () => {
519
+ const result: RouteCallResult = await callRoute({
520
+ method: "POST",
521
+ uri: REMINDER_ROUTE,
522
+ req: buildExpiredSessionRequest({
523
+ tenantId: undefined,
524
+ body: { userIds: [subjectUserId.toString()] },
525
+ }),
526
+ });
527
+
528
+ expectAskedToAuthenticate(result);
529
+ expect(result.status).not.toBe(ExceptionCode.BadDataException);
530
+ expectNoReadinessWork();
531
+ });
532
+
533
+ test("is 401 even when the body is malformed - the body is never read for a caller with no session", async () => {
534
+ const result: RouteCallResult = await callRoute({
535
+ method: "POST",
536
+ uri: REMINDER_ROUTE,
537
+ req: buildExpiredSessionRequest({
538
+ tenantId: projectId,
539
+ body: { userIds: "not-even-an-array" } as unknown as JSONObject,
540
+ }),
541
+ });
542
+
543
+ expectAskedToAuthenticate(result);
544
+ expectNoReadinessWork();
545
+ });
546
+
547
+ test("is 401 when the body names a project of its own - nothing in the request can stand in for a session", async () => {
548
+ const result: RouteCallResult = await callRoute({
549
+ method: "POST",
550
+ uri: REMINDER_ROUTE,
551
+ req: buildExpiredSessionRequest({
552
+ tenantId: undefined,
553
+ body: {
554
+ projectId: projectId.toString(),
555
+ userIds: [subjectUserId.toString()],
556
+ },
557
+ }),
558
+ });
559
+
560
+ expectAskedToAuthenticate(result);
561
+ expectNoReadinessWork();
562
+ });
563
+
564
+ test("a stale access token that IS still sent is refused 401 by the middleware, before the handler", async () => {
565
+ /*
566
+ * The other way an expired session arrives: a client that keeps the token
567
+ * past its lifetime (the mobile app, or a cookie that outlived its JWT).
568
+ * getUserMiddleware cannot decode it and answers 401 itself.
569
+ */
570
+ const req: ExpressRequest = buildExpiredSessionRequest({
571
+ tenantId: projectId,
572
+ body: { userIds: [subjectUserId.toString()] },
573
+ });
574
+
575
+ (req as unknown as { cookies: Dictionary<string> }).cookies[
576
+ CookieUtil.getUserTokenKey()
577
+ ] = "an.expired.jwt";
578
+
579
+ const result: RouteCallResult = await callRoute({
580
+ method: "POST",
581
+ uri: REMINDER_ROUTE,
582
+ req: req,
583
+ });
584
+
585
+ expect(result.reachedHandler).toBe(false);
586
+ expect(result.status).toBe(401);
587
+ expect(result.errorBody?.["message"]).toBe(
588
+ "AccessToken is invalid or expired. Please refresh your token.",
589
+ );
590
+ expectNoReadinessWork();
591
+ });
592
+
593
+ test("the refreshed replay of the same POST succeeds, and the reminder goes out exactly once", async () => {
594
+ /*
595
+ * The whole point of answering 401: the client refreshes the session and
596
+ * resends the identical request. First attempt refused without side
597
+ * effects, second attempt (same body, now with a session) sends.
598
+ */
599
+ const body: JSONObject = { userIds: [subjectUserId.toString()] };
600
+
601
+ const expired: RouteCallResult = await callRoute({
602
+ method: "POST",
603
+ uri: REMINDER_ROUTE,
604
+ req: buildExpiredSessionRequest({ tenantId: projectId, body: body }),
605
+ });
606
+
607
+ expectAskedToAuthenticate(expired);
608
+ expect(sendRemindersSpy).not.toHaveBeenCalled();
609
+
610
+ const replayed: RouteCallResult = await callRoute({
611
+ method: "POST",
612
+ uri: REMINDER_ROUTE,
613
+ req: buildSignedInRequest({
614
+ tenantId: projectId,
615
+ userId: callerUserId,
616
+ userType: UserType.User,
617
+ permissions: [Permission.ProjectOwner],
618
+ body: body,
619
+ }),
620
+ signedIn: true,
621
+ });
622
+
623
+ expect(replayed.thrownToNext).toBeUndefined();
624
+ expect(replayed.status).toBeUndefined();
625
+ expect(sendRemindersSpy).toHaveBeenCalledTimes(1);
626
+ expect(sendJsonSpy).toHaveBeenCalledTimes(1);
627
+ });
628
+ });
629
+
630
+ describe("POST /on-call-readiness/send-setup-reminder for signed-in callers is unchanged", () => {
631
+ test("a privileged member sends, to the project from their tenant", async () => {
632
+ const result: RouteCallResult = await callRoute({
633
+ method: "POST",
634
+ uri: REMINDER_ROUTE,
635
+ req: buildSignedInRequest({
636
+ tenantId: projectId,
637
+ userId: callerUserId,
638
+ userType: UserType.User,
639
+ permissions: [Permission.ProjectMember, Permission.OnCallAdmin],
640
+ body: { userIds: [subjectUserId.toString()] },
641
+ }),
642
+ signedIn: true,
643
+ });
644
+
645
+ expect(result.thrownToNext).toBeUndefined();
646
+ expect(sendRemindersSpy).toHaveBeenCalledTimes(1);
647
+
648
+ const call: { projectId: ObjectID; userIds: Array<ObjectID> } =
649
+ sendRemindersSpy.mock.calls[0]![0] as {
650
+ projectId: ObjectID;
651
+ userIds: Array<ObjectID>;
652
+ };
653
+
654
+ expect(call.projectId.toString()).toBe(projectId.toString());
655
+ expect(
656
+ call.userIds.map((id: ObjectID): string => {
657
+ return id.toString();
658
+ }),
659
+ ).toEqual([subjectUserId.toString()]);
660
+
661
+ const payload: JSONObject = sendJsonSpy.mock.calls[0]![2] as JSONObject;
662
+
663
+ expect(payload["sentCount"]).toBe(1);
664
+ });
665
+
666
+ test("a member without the reminder permission still gets the 422 refusal, and nothing is sent", async () => {
667
+ /*
668
+ * A real, current session - so NOT a 401. Answering 401 here would make
669
+ * the dashboard refresh a perfectly good session and replay the POST into
670
+ * the same refusal.
671
+ */
672
+ const result: RouteCallResult = await callRoute({
673
+ method: "POST",
674
+ uri: REMINDER_ROUTE,
675
+ req: buildSignedInRequest({
676
+ tenantId: projectId,
677
+ userId: callerUserId,
678
+ userType: UserType.User,
679
+ permissions: [Permission.ProjectMember],
680
+ body: { userIds: [subjectUserId.toString()] },
681
+ }),
682
+ signedIn: true,
683
+ });
684
+
685
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
686
+ expect(result.thrownToNext).not.toBeInstanceOf(NotAuthenticatedException);
687
+ expect((result.thrownToNext as Exception).code).toBe(
688
+ ExceptionCode.NotAuthorizedException,
689
+ );
690
+ expect(result.status).toBe(422);
691
+ expect(result.errorBody?.["message"]).toBe(REFUSAL);
692
+ expectNoReadinessWork();
693
+ });
694
+
695
+ test("an owner of ANOTHER project naming this one in the header still gets the 422", async () => {
696
+ const req: ExpressRequest = buildSignedInRequest({
697
+ tenantId: otherProjectId,
698
+ userId: callerUserId,
699
+ userType: UserType.User,
700
+ permissions: [Permission.ProjectOwner],
701
+ body: { userIds: [subjectUserId.toString()] },
702
+ });
703
+
704
+ // The header says this project; the grants are for the other one.
705
+ (req as OneUptimeRequest).tenantId = projectId;
706
+ req.headers["tenantid"] = projectId.toString();
707
+
708
+ const result: RouteCallResult = await callRoute({
709
+ method: "POST",
710
+ uri: REMINDER_ROUTE,
711
+ req: req,
712
+ signedIn: true,
713
+ });
714
+
715
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
716
+ expect(result.status).toBe(422);
717
+ expectNoReadinessWork();
718
+ });
719
+
720
+ test("a signed-in caller with no tenant header still gets the 400", async () => {
721
+ const req: ExpressRequest = buildSignedInRequest({
722
+ tenantId: projectId,
723
+ userId: callerUserId,
724
+ userType: UserType.User,
725
+ permissions: [Permission.ProjectOwner],
726
+ body: { userIds: [subjectUserId.toString()] },
727
+ });
728
+
729
+ // Signed in, but the call carried no tenant header.
730
+ (req as OneUptimeRequest).tenantId = undefined as unknown as ObjectID;
731
+ req.headers = {};
732
+
733
+ const result: RouteCallResult = await callRoute({
734
+ method: "POST",
735
+ uri: REMINDER_ROUTE,
736
+ req: req,
737
+ signedIn: true,
738
+ });
739
+
740
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
741
+ expect(result.status).toBe(400);
742
+ expectNoReadinessWork();
743
+ });
744
+
745
+ test("a project API key - authenticated, but not a person - gets the 422, not a 401", async () => {
746
+ /*
747
+ * An API client has no session to refresh, so a 401 would send it round a
748
+ * refresh it can never complete. The route is for members; the key is
749
+ * refused as not authorised, exactly as before.
750
+ */
751
+ const result: RouteCallResult = await callRoute({
752
+ method: "POST",
753
+ uri: REMINDER_ROUTE,
754
+ req: buildSignedInRequest({
755
+ tenantId: projectId,
756
+ userId: undefined,
757
+ userType: UserType.API,
758
+ permissions: [Permission.ProjectOwner],
759
+ body: { userIds: [subjectUserId.toString()] },
760
+ }),
761
+ signedIn: true,
762
+ });
763
+
764
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
765
+ expect(result.status).toBe(422);
766
+ expect(result.errorBody?.["message"]).toBe(REFUSAL);
767
+ expectNoReadinessWork();
768
+ });
769
+ });
770
+
771
+ describe("the readiness GET routes with an expired session", () => {
772
+ type ReadCase = {
773
+ name: string;
774
+ uri: string;
775
+ };
776
+
777
+ const reads: Array<ReadCase> = [
778
+ { name: "GET /on-call-readiness/policy/:policyId", uri: POLICY_ROUTE },
779
+ { name: "GET /on-call-readiness/project", uri: PROJECT_ROUTE },
780
+ { name: "GET /on-call-readiness/user/:userId", uri: USER_ROUTE },
781
+ ];
782
+
783
+ // Ids are generated per test, so the path params are filled in here.
784
+ function paramsFor(uri: string): Dictionary<string> {
785
+ if (uri === POLICY_ROUTE) {
786
+ return { policyId: policyId.toString() };
787
+ }
788
+
789
+ if (uri === USER_ROUTE) {
790
+ return { userId: subjectUserId.toString() };
791
+ }
792
+
793
+ return {};
794
+ }
795
+
796
+ test.each(reads)(
797
+ "$name answers 401 and does no readiness work",
798
+ async (read: ReadCase) => {
799
+ const result: RouteCallResult = await callRoute({
800
+ method: "GET",
801
+ uri: read.uri,
802
+ req: buildExpiredSessionRequest({
803
+ tenantId: projectId,
804
+ params: paramsFor(read.uri),
805
+ }),
806
+ });
807
+
808
+ expect(result.reachedHandler).toBe(true);
809
+ expectAskedToAuthenticate(result);
810
+ expectNoReadinessWork();
811
+ },
812
+ );
813
+
814
+ test.each(reads)(
815
+ "$name with ?refresh=true does not clear the shared readiness cache for an anonymous caller",
816
+ async (read: ReadCase) => {
817
+ const result: RouteCallResult = await callRoute({
818
+ method: "GET",
819
+ uri: read.uri,
820
+ req: buildExpiredSessionRequest({
821
+ tenantId: projectId,
822
+ params: paramsFor(read.uri),
823
+ query: { refresh: "true" },
824
+ }),
825
+ });
826
+
827
+ expectAskedToAuthenticate(result);
828
+ expect(clearCacheSpy).not.toHaveBeenCalled();
829
+ },
830
+ );
831
+
832
+ test.each(reads)(
833
+ "$name answers 401 without a tenant header too",
834
+ async (read: ReadCase) => {
835
+ const result: RouteCallResult = await callRoute({
836
+ method: "GET",
837
+ uri: read.uri,
838
+ req: buildExpiredSessionRequest({
839
+ tenantId: undefined,
840
+ params: paramsFor(read.uri),
841
+ }),
842
+ });
843
+
844
+ expectAskedToAuthenticate(result);
845
+ expectNoReadinessWork();
846
+ },
847
+ );
848
+
849
+ test("a malformed path id from an anonymous caller is still the 401, not the 400", async () => {
850
+ const result: RouteCallResult = await callRoute({
851
+ method: "GET",
852
+ uri: POLICY_ROUTE,
853
+ req: buildExpiredSessionRequest({
854
+ tenantId: projectId,
855
+ params: { policyId: "not-a-uuid" },
856
+ }),
857
+ });
858
+
859
+ expectAskedToAuthenticate(result);
860
+ expectNoReadinessWork();
861
+ });
862
+
863
+ test("a signed-in member still reads the project summary (the reads keep the membership bar)", async () => {
864
+ const result: RouteCallResult = await callRoute({
865
+ method: "GET",
866
+ uri: PROJECT_ROUTE,
867
+ req: buildSignedInRequest({
868
+ tenantId: projectId,
869
+ userId: callerUserId,
870
+ userType: UserType.User,
871
+ permissions: [Permission.ProjectMember],
872
+ }),
873
+ signedIn: true,
874
+ });
875
+
876
+ expect(result.thrownToNext).toBeUndefined();
877
+ expect(result.status).toBeUndefined();
878
+ expect(projectReadinessSpy).toHaveBeenCalledTimes(1);
879
+ expect(sendJsonSpy).toHaveBeenCalledTimes(1);
880
+ });
881
+
882
+ test("a signed-in member reads a policy of their own project", async () => {
883
+ const req: ExpressRequest = buildSignedInRequest({
884
+ tenantId: projectId,
885
+ userId: callerUserId,
886
+ userType: UserType.User,
887
+ permissions: [Permission.ProjectMember],
888
+ });
889
+
890
+ (req as unknown as { params: Dictionary<string> }).params = {
891
+ policyId: policyId.toString(),
892
+ };
893
+
894
+ const result: RouteCallResult = await callRoute({
895
+ method: "GET",
896
+ uri: POLICY_ROUTE,
897
+ req: req,
898
+ signedIn: true,
899
+ });
900
+
901
+ expect(result.thrownToNext).toBeUndefined();
902
+ expect(policyReadinessSpy).toHaveBeenCalledTimes(1);
903
+ });
904
+
905
+ test("a signed-in caller who is not a member of the named project still gets the 422", async () => {
906
+ const req: ExpressRequest = buildSignedInRequest({
907
+ tenantId: otherProjectId,
908
+ userId: callerUserId,
909
+ userType: UserType.User,
910
+ permissions: [Permission.ProjectMember],
911
+ });
912
+
913
+ (req as OneUptimeRequest).tenantId = projectId;
914
+ req.headers["tenantid"] = projectId.toString();
915
+
916
+ const result: RouteCallResult = await callRoute({
917
+ method: "GET",
918
+ uri: PROJECT_ROUTE,
919
+ req: req,
920
+ signedIn: true,
921
+ });
922
+
923
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
924
+ expect(result.status).toBe(422);
925
+ expect(result.errorBody?.["message"]).toBe(REFUSAL);
926
+ expectNoReadinessWork();
927
+ });
928
+ });