@oneuptime/common 13.0.7 → 14.0.0

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 (759) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Monitor.ts +3 -2
  4. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
  7. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
  8. package/Scripts/generate-service-worker.js +65 -4
  9. package/Server/API/AIBillingAPI.ts +1 -0
  10. package/Server/API/AIInsightAPI.ts +1 -4
  11. package/Server/API/AIInvestigationAPI.ts +1 -4
  12. package/Server/API/AIReadinessAPI.ts +4 -5
  13. package/Server/API/AlertAPI.ts +1 -0
  14. package/Server/API/AutoRemediationAPI.ts +1 -4
  15. package/Server/API/BaseAPI.ts +34 -0
  16. package/Server/API/BaseAnalyticsAPI.ts +80 -9
  17. package/Server/API/BillingAPI.ts +2 -0
  18. package/Server/API/BillingInvoiceAPI.ts +1 -0
  19. package/Server/API/BillingPaymentMethodAPI.ts +2 -0
  20. package/Server/API/CodeFixRunAPI.ts +1 -3
  21. package/Server/API/CommonAPI.ts +68 -4
  22. package/Server/API/DashboardAPI.ts +12 -12
  23. package/Server/API/GlobalConfigAPI.ts +243 -460
  24. package/Server/API/IncidentAPI.ts +2 -0
  25. package/Server/API/IncidentEpisodeAPI.ts +1 -0
  26. package/Server/API/NotificationAPI.ts +1 -0
  27. package/Server/API/OnCallCalendarAPI.ts +4 -5
  28. package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
  29. package/Server/API/OnCallReadinessAPI.ts +4 -1
  30. package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
  31. package/Server/API/ProjectAPI.ts +1 -0
  32. package/Server/API/ProjectOIDC.ts +25 -16
  33. package/Server/API/ProjectSSO.ts +24 -16
  34. package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
  35. package/Server/API/StatusPageAPI.ts +261 -74
  36. package/Server/API/TeamMemberAPI.ts +13 -1
  37. package/Server/API/UserCallAPI.ts +2 -0
  38. package/Server/API/UserEmailAPI.ts +2 -0
  39. package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
  40. package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
  41. package/Server/API/UserSlackAPI.ts +1 -0
  42. package/Server/API/UserSmsAPI.ts +2 -0
  43. package/Server/API/UserTotpAuthAPI.ts +1 -0
  44. package/Server/API/UserWebAuthnAPI.ts +2 -0
  45. package/Server/API/UserWebhookAPI.ts +1 -0
  46. package/Server/API/UserWhatsAppAPI.ts +2 -0
  47. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  48. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  49. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  50. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  51. package/Server/EnvironmentConfig.ts +147 -2
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  54. package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
  55. package/Server/Middleware/UserAuthorization.ts +117 -21
  56. package/Server/Services/ApiKeyPermissionService.ts +101 -4
  57. package/Server/Services/AuditLogService.ts +84 -1079
  58. package/Server/Services/DatabaseService.ts +245 -0
  59. package/Server/Services/DetectionRuleService.ts +5 -4
  60. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  61. package/Server/Services/GlobalConfigService.ts +80 -1
  62. package/Server/Services/MonitorProbeService.ts +116 -20
  63. package/Server/Services/MonitorService.ts +56 -4
  64. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  65. package/Server/Services/MonitorTemplateService.ts +26 -0
  66. package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
  67. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
  69. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
  70. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
  71. package/Server/Services/PayAsYouGoBillingService.ts +23 -28
  72. package/Server/Services/ProjectService.ts +302 -0
  73. package/Server/Services/RumSessionPinService.ts +293 -22
  74. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  75. package/Server/Services/StatusPageService.ts +78 -1
  76. package/Server/Services/TeamMemberService.ts +11 -0
  77. package/Server/Services/TeamPermissionService.ts +120 -26
  78. package/Server/Services/TeamService.ts +10 -0
  79. package/Server/Services/ThreatIntelFeedService.ts +6 -3
  80. package/Server/Services/UserService.ts +10 -17
  81. package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
  82. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  83. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  84. package/Server/Types/Database/Permissions/Index.ts +75 -11
  85. package/Server/Types/Database/Permissions/TablePermission.ts +34 -31
  86. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  87. package/Server/Types/Markdown.ts +41 -0
  88. package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
  89. package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
  90. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  91. package/Server/Utils/EditionEnforcement.ts +233 -0
  92. package/Server/Utils/FrontendEnvironment.ts +41 -2
  93. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  94. package/Server/Utils/MasterAdminPageAccess.ts +61 -0
  95. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  96. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  97. package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
  98. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  99. package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
  100. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  101. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
  102. package/Server/Utils/Realtime.ts +163 -45
  103. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
  104. package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
  105. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
  106. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
  107. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
  108. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  109. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  110. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  111. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  112. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  113. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  114. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  115. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  116. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
  117. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
  118. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
  119. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
  120. package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
  121. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  122. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
  123. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
  124. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
  125. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
  126. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  127. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  128. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
  129. package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
  130. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
  131. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  132. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
  133. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  134. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
  135. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
  136. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
  137. package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
  138. package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
  139. package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
  140. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +133 -70
  141. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  142. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  143. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
  144. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -58
  145. package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
  146. package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
  147. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  148. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
  149. package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
  150. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  151. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
  152. package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
  153. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
  154. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  155. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
  156. package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
  157. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  158. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  159. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  160. package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
  161. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  162. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
  163. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
  164. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
  165. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
  166. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  167. package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
  168. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
  169. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
  170. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
  171. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
  172. package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
  173. package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
  174. package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
  175. package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
  176. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
  177. package/Tests/Server/API/GlobalConfigLicense.test.ts +766 -381
  178. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
  179. package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
  180. package/Tests/Server/API/OnCallReadinessAPI.test.ts +196 -912
  181. package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
  182. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
  183. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
  184. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
  185. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
  186. package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
  187. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  188. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
  189. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
  190. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
  191. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  192. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
  193. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
  194. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
  195. package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
  196. package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
  197. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
  198. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
  199. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
  200. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
  201. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  202. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  203. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  204. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  205. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  206. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  207. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  208. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  209. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  210. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  211. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  212. package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
  213. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
  214. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
  215. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  216. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  217. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
  218. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
  219. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
  220. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  221. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  222. package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
  223. package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
  224. package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
  225. package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
  226. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  227. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  228. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  229. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
  230. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  231. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  232. package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
  233. package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
  234. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
  235. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
  236. package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
  237. package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
  238. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  239. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  240. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
  241. package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
  242. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  243. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  244. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  245. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  246. package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
  247. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
  248. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
  249. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  250. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
  251. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  252. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  253. package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
  254. package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
  255. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
  256. package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
  257. package/Tests/Server/Types/Markdown.test.ts +181 -0
  258. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
  259. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
  260. package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
  261. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
  262. package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
  263. package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
  264. package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
  265. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  266. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  267. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  268. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  269. package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
  270. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  271. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  272. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
  273. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  274. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  275. package/Tests/Server/Utils/Realtime.test.ts +502 -0
  276. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
  277. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
  278. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
  279. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
  280. package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
  281. package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
  282. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
  283. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
  284. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
  285. package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
  286. package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
  287. package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
  288. package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
  289. package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
  290. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  291. package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
  292. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  293. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  294. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
  295. package/Tests/Types/Call/CallProvider.test.ts +528 -0
  296. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  297. package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
  298. package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
  299. package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
  300. package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
  301. package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
  302. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
  303. package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
  304. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  305. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  306. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +165 -171
  307. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  308. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  309. package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
  310. package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
  311. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
  312. package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
  313. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  314. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  315. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  316. package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
  317. package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
  318. package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
  319. package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
  320. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  321. package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
  322. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  323. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
  324. package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
  325. package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
  326. package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
  327. package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
  328. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  329. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  330. package/Tests/UI/Utils/Cookie.test.ts +440 -0
  331. package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
  332. package/Tests/UI/Utils/Navigation.test.ts +33 -0
  333. package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
  334. package/Tests/UI/Utils/Realtime.test.ts +759 -0
  335. package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
  336. package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
  337. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  338. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  339. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  340. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
  341. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  342. package/Types/API/Hostname.ts +63 -16
  343. package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
  344. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  345. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  346. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  347. package/Types/Database/CanonicalModelInstance.ts +102 -0
  348. package/Types/Database/TableColumn.ts +20 -6
  349. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  350. package/Types/IP/IPv6.ts +6 -1
  351. package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
  352. package/Types/Monitor/MonitorStep.ts +6 -1
  353. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  354. package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
  355. package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
  356. package/Types/Realtime/EventName.ts +11 -0
  357. package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
  358. package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
  359. package/Types/SecurityEvent/SigmaRule.ts +51 -0
  360. package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
  361. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  362. package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
  363. package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
  364. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  365. package/UI/Components/EditionLabel/EditionLabel.tsx +808 -1195
  366. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  367. package/UI/Components/EventItem/EventItem.tsx +12 -1
  368. package/UI/Components/Forms/BasicForm.tsx +151 -126
  369. package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
  370. package/UI/Components/Forms/Fields/FormField.tsx +10 -4
  371. package/UI/Components/Forms/FormSummary.tsx +7 -0
  372. package/UI/Components/Forms/Types/Field.ts +10 -0
  373. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  374. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  375. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
  376. package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
  377. package/UI/Components/ModelList/ModelList.tsx +11 -1
  378. package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
  379. package/UI/Components/ModelTable/TableView.tsx +1 -1
  380. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  381. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  382. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
  383. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  384. package/UI/Utils/API/API.ts +173 -12
  385. package/UI/Utils/API/RequestOptions.ts +11 -0
  386. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
  387. package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
  388. package/UI/Utils/Navigation.ts +9 -0
  389. package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
  390. package/UI/Utils/Realtime.ts +176 -30
  391. package/UI/esbuild-enterprise.js +263 -0
  392. package/Utils/API.ts +14 -5
  393. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  394. package/Utils/HostAddressUtil.ts +94 -0
  395. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  396. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  397. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
  398. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
  399. package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
  400. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  401. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  402. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  403. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  404. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  405. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  406. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  407. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  408. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  409. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
  410. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
  411. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
  412. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
  413. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
  414. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
  415. package/build/dist/Server/API/AIBillingAPI.js +1 -1
  416. package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
  417. package/build/dist/Server/API/AIInsightAPI.js +1 -4
  418. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  419. package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
  420. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  421. package/build/dist/Server/API/AIReadinessAPI.js +1 -3
  422. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
  423. package/build/dist/Server/API/AlertAPI.js +1 -1
  424. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  425. package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
  426. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  427. package/build/dist/Server/API/BaseAPI.js +33 -0
  428. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  429. package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
  430. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  431. package/build/dist/Server/API/BillingAPI.js +2 -2
  432. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  433. package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
  434. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  435. package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
  436. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  437. package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
  438. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  439. package/build/dist/Server/API/CommonAPI.js +50 -5
  440. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  441. package/build/dist/Server/API/DashboardAPI.js +12 -12
  442. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  443. package/build/dist/Server/API/GlobalConfigAPI.js +158 -342
  444. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  445. package/build/dist/Server/API/IncidentAPI.js +2 -2
  446. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  447. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  448. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  449. package/build/dist/Server/API/NotificationAPI.js +1 -1
  450. package/build/dist/Server/API/NotificationAPI.js.map +1 -1
  451. package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
  452. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
  453. package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
  454. package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
  455. package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
  456. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
  457. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
  458. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
  459. package/build/dist/Server/API/ProjectAPI.js +1 -1
  460. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  461. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  462. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  463. package/build/dist/Server/API/ProjectSSO.js +24 -16
  464. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  465. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  466. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  467. package/build/dist/Server/API/StatusPageAPI.js +218 -76
  468. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  469. package/build/dist/Server/API/TeamMemberAPI.js +11 -4
  470. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  471. package/build/dist/Server/API/UserCallAPI.js +2 -2
  472. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  473. package/build/dist/Server/API/UserEmailAPI.js +2 -2
  474. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  475. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
  476. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  477. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
  478. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
  479. package/build/dist/Server/API/UserSlackAPI.js +1 -1
  480. package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
  481. package/build/dist/Server/API/UserSmsAPI.js +2 -2
  482. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  483. package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
  484. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  485. package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
  486. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  487. package/build/dist/Server/API/UserWebhookAPI.js +1 -1
  488. package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
  489. package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
  490. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  491. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  492. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  493. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  494. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  495. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  496. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  497. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  498. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  499. package/build/dist/Server/EnvironmentConfig.js +84 -2
  500. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  501. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  502. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  503. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  504. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  505. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
  506. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  507. package/build/dist/Server/Middleware/UserAuthorization.js +94 -15
  508. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  509. package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
  510. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  511. package/build/dist/Server/Services/AuditLogService.js +71 -740
  512. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  513. package/build/dist/Server/Services/DatabaseService.js +171 -0
  514. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  515. package/build/dist/Server/Services/DetectionRuleService.js +4 -4
  516. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  517. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  518. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  519. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  520. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  521. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  522. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  523. package/build/dist/Server/Services/MonitorService.js +45 -4
  524. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  525. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  526. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  527. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  528. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  529. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
  530. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
  531. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
  532. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  533. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
  534. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  535. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
  536. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  537. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
  538. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  539. package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
  540. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
  541. package/build/dist/Server/Services/ProjectService.js +231 -0
  542. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  543. package/build/dist/Server/Services/RumSessionPinService.js +198 -16
  544. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  545. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  546. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  547. package/build/dist/Server/Services/StatusPageService.js +71 -0
  548. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  549. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  550. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  551. package/build/dist/Server/Services/TeamPermissionService.js +89 -27
  552. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  553. package/build/dist/Server/Services/TeamService.js +9 -0
  554. package/build/dist/Server/Services/TeamService.js.map +1 -1
  555. package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
  556. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
  557. package/build/dist/Server/Services/UserService.js +10 -16
  558. package/build/dist/Server/Services/UserService.js.map +1 -1
  559. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
  560. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  561. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  562. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  563. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  564. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  565. package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
  566. package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
  567. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +24 -16
  568. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  569. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  570. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  571. package/build/dist/Server/Types/Markdown.js +31 -0
  572. package/build/dist/Server/Types/Markdown.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
  574. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
  576. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
  577. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  578. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  579. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  580. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  581. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  582. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  583. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  584. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  585. package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
  586. package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
  587. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  588. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  589. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  590. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  591. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
  592. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  593. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  594. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  595. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
  596. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  597. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  598. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  599. package/build/dist/Server/Utils/Realtime.js +126 -37
  600. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  601. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
  602. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  603. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
  604. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
  605. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
  606. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  607. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
  608. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
  609. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
  610. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
  611. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  612. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  613. package/build/dist/Types/API/Hostname.js +59 -11
  614. package/build/dist/Types/API/Hostname.js.map +1 -1
  615. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
  616. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
  617. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  618. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  619. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  620. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  621. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  622. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  623. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  624. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  625. package/build/dist/Types/Database/TableColumn.js +19 -6
  626. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  627. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  628. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  629. package/build/dist/Types/IP/IPv6.js +6 -1
  630. package/build/dist/Types/IP/IPv6.js.map +1 -1
  631. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  632. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  633. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  634. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
  635. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
  636. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
  637. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
  638. package/build/dist/Types/Realtime/EventName.js +10 -0
  639. package/build/dist/Types/Realtime/EventName.js.map +1 -1
  640. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
  641. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  642. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
  643. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
  644. package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
  645. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
  646. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
  647. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
  648. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  649. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  650. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
  651. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
  652. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
  653. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
  654. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
  655. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  656. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +425 -585
  657. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  658. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  659. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  660. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  661. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  662. package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
  663. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  664. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
  665. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
  666. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
  667. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  668. package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
  669. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  670. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  671. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  672. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  673. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  674. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
  675. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  676. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
  677. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
  678. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  679. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  680. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
  681. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  682. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  683. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  684. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  685. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  686. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  687. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  688. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
  689. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  690. package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
  691. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  692. package/build/dist/UI/Utils/API/API.js +143 -19
  693. package/build/dist/UI/Utils/API/API.js.map +1 -1
  694. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
  695. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  696. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
  697. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  698. package/build/dist/UI/Utils/Navigation.js +8 -0
  699. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  700. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
  701. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
  702. package/build/dist/UI/Utils/Realtime.js +120 -15
  703. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  704. package/build/dist/Utils/API.js +13 -5
  705. package/build/dist/Utils/API.js.map +1 -1
  706. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  707. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  708. package/build/dist/Utils/HostAddressUtil.js +80 -0
  709. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  710. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  711. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  712. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  713. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  714. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
  715. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  716. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
  717. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
  718. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
  719. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  720. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  721. package/jest.config.json +3 -1
  722. package/package.json +1 -1
  723. package/test-setup.sh +103 -34
  724. package/tsconfig.json +13 -0
  725. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  726. package/Server/API/TeamComplianceAPI.ts +0 -158
  727. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  728. package/Server/Services/TeamComplianceService.ts +0 -733
  729. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  730. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  731. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  732. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  733. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  734. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  735. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  736. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  737. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  738. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  739. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  740. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  741. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  742. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  743. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  744. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  745. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  746. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  747. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  748. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  749. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  750. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  751. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  752. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  753. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  754. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  755. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  756. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  757. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  758. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  759. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -1,7 +1,5 @@
1
1
  import { mockRouter } from "./Helpers";
2
2
  import OnCallReadinessAPI from "../../../Server/API/OnCallReadinessAPI";
3
- import TeamComplianceAPI from "../../../Server/API/TeamComplianceAPI";
4
- import TeamComplianceService from "../../../Server/Services/TeamComplianceService";
5
3
  import CommonAPI from "../../../Server/API/CommonAPI";
6
4
  import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
7
5
  import OnCallReadinessService, {
@@ -21,9 +19,7 @@ import OnCallDutyPolicyScheduleLayerUserService from "../../../Server/Services/O
21
19
  import OnCallDutyPolicyService from "../../../Server/Services/OnCallDutyPolicyService";
22
20
  import OnCallDutyPolicyUserOverrideService from "../../../Server/Services/OnCallDutyPolicyUserOverrideService";
23
21
  import ProjectService from "../../../Server/Services/ProjectService";
24
- import TeamComplianceSettingService from "../../../Server/Services/TeamComplianceSettingService";
25
22
  import TeamMemberService from "../../../Server/Services/TeamMemberService";
26
- import TeamService from "../../../Server/Services/TeamService";
27
23
  import UserCallService from "../../../Server/Services/UserCallService";
28
24
  import UserEmailService from "../../../Server/Services/UserEmailService";
29
25
  import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
@@ -51,11 +47,13 @@ import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
51
47
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
52
48
  import UserWhatsApp from "../../../Models/DatabaseModels/UserWhatsApp";
53
49
  import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
54
- import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
55
50
  import Dictionary from "../../../Types/Dictionary";
56
51
  import Email from "../../../Types/Email";
57
52
  import BadDataException from "../../../Types/Exception/BadDataException";
58
53
  import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
54
+ import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
55
+ import Exception from "../../../Types/Exception/Exception";
56
+ import ExceptionCode from "../../../Types/Exception/ExceptionCode";
59
57
  import NotificationRuleType from "../../../Types/NotificationRule/NotificationRuleType";
60
58
  import ObjectID from "../../../Types/ObjectID";
61
59
  import Permission, {
@@ -63,19 +61,16 @@ import Permission, {
63
61
  UserTenantAccessPermission,
64
62
  } from "../../../Types/Permission";
65
63
  import Phone from "../../../Types/Phone";
66
- import ComplianceRuleType from "../../../Types/Team/ComplianceRuleType";
67
- import {
68
- afterEach,
69
- beforeAll,
70
- beforeEach,
71
- describe,
72
- expect,
73
- test,
74
- } from "@jest/globals";
64
+ import UserType from "../../../Types/UserType";
65
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
75
66
 
76
67
  /*
77
- * The HTTP face of Phase 2, plus the TeamComplianceService rebuild that now sits
78
- * behind it.
68
+ * The HTTP face of Phase 2.
69
+ *
70
+ * The team compliance route (/team/compliance-status/:teamId) and the
71
+ * TeamComplianceService rebuild behind it are Enterprise Edition code since the
72
+ * Community / Enterprise split; their HTTP tests moved with them to
73
+ * ee/Tests/Server/TeamCompliance/TeamComplianceAPI.test.ts.
79
74
  *
80
75
  * Two different levels of test live in this file on purpose, because the two
81
76
  * risks are different:
@@ -88,22 +83,26 @@ import {
88
83
  * answered identically so the endpoints cannot be used to enumerate ids
89
84
  * across tenants.
90
85
  *
91
- * MASKING AND THE COMPLIANCE REBUILD are tested end to end, with the REAL
92
- * OnCallReadinessService and only the database-facing services stubbed. A
93
- * masking test that stubs the service it is testing proves nothing: the whole
94
- * claim is that a raw phone number put into the database cannot come out of
95
- * the HTTP response, and the only way to test that claim is to put a raw phone
96
- * number in one end and read the JSON out of the other. The same reasoning
97
- * applies to the compliance rebuild - TeamComplianceServiceBehaviour.test.ts
98
- * already pins its mapping with readiness stubbed, so what is left worth
99
- * proving is that the real seam between the two behaves, which is exactly
100
- * where the four defects used to live.
86
+ * MASKING is tested end to end, with the REAL OnCallReadinessService and only
87
+ * the database-facing services stubbed. A masking test that stubs the service
88
+ * it is testing proves nothing: the whole claim is that a raw phone number put
89
+ * into the database cannot come out of the HTTP response, and the only way to
90
+ * test that claim is to put a raw phone number in one end and read the JSON
91
+ * out of the other.
101
92
  *
102
93
  * These routes are mounted with UserMiddleware.getUserMiddleware, which admits
103
94
  * anonymous callers as UserType.Public and takes the project from a
104
95
  * caller-supplied `tenantid` header, and everything underneath reads with
105
96
  * isRoot: true. The handlers are therefore the only gate there is, which is why
106
97
  * a disproportionate share of this file is spent on who gets refused.
98
+ *
99
+ * And on HOW they are refused. A caller with no credentials at all is answered
100
+ * with NotAuthenticatedException (401) before the tenant header or the path is
101
+ * looked at; an authenticated caller who may not see the data keeps the 422.
102
+ * In practice the anonymous caller is an admin whose access-token cookie
103
+ * expired while the page sat open, and the dashboard refreshes the session and
104
+ * resends only on a 401 - a 422 left them looking at "not authorized" on their
105
+ * own project.
107
106
  */
108
107
 
109
108
  jest.mock("../../../Server/Utils/Express", () => {
@@ -139,7 +138,6 @@ const POLICY_ROUTE: string = "/on-call-readiness/policy/:policyId";
139
138
  const PROJECT_ROUTE: string = "/on-call-readiness/project";
140
139
  const USER_ROUTE: string = "/on-call-readiness/user/:userId";
141
140
  const SETUP_REMINDER_ROUTE: string = "/on-call-readiness/send-setup-reminder";
142
- const COMPLIANCE_ROUTE: string = "/team/compliance-status/:teamId";
143
141
 
144
142
  /*
145
143
  * The message every authorisation refusal on these routes carries. Asserting on
@@ -148,6 +146,24 @@ const COMPLIANCE_ROUTE: string = "/team/compliance-status/:teamId";
148
146
  */
149
147
  const REFUSAL: string = "You are not authorized to access this project's data.";
150
148
 
149
+ /*
150
+ * The refusal for a caller with no credentials. Deliberately NOT the sentence
151
+ * above: it is decided before anything project-specific is consulted, so it is
152
+ * the same answer for every project and every id and discloses nothing - and
153
+ * it has to be a 401, because that is the only status the browser client
154
+ * answers by refreshing the session.
155
+ */
156
+ function expectAuthenticationRequired(thrown: unknown): void {
157
+ expect(thrown).toBeInstanceOf(NotAuthenticatedException);
158
+ expect((thrown as Exception).code).toBe(
159
+ ExceptionCode.NotAuthenticatedException,
160
+ );
161
+ expect((thrown as Exception).code).toBe(401);
162
+ expect((thrown as Exception).message).toBe(
163
+ CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE,
164
+ );
165
+ }
166
+
151
167
  /** Enough of a findBy argument to assert on, without importing FindBy generics. */
152
168
  interface CapturedFindBy {
153
169
  query: Record<string, unknown>;
@@ -178,19 +194,6 @@ interface RouteCallResult {
178
194
  nextCallCount: number;
179
195
  }
180
196
 
181
- /** A minimal user row, as UserService.findBy would hand one back. */
182
- interface StubUser {
183
- id: ObjectID;
184
- name: string;
185
- email: string;
186
- }
187
-
188
- /** A minimal severity row - the shape both severity services return here. */
189
- interface StubSeverity {
190
- id: ObjectID;
191
- name: string;
192
- }
193
-
194
197
  function buildMemberProps(data: {
195
198
  projectId: ObjectID;
196
199
  userId: ObjectID;
@@ -305,7 +308,7 @@ function firstCall(spy: jest.SpyInstance): CapturedFindBy {
305
308
 
306
309
  let propsSpy: jest.SpyInstance;
307
310
 
308
- // Every database-facing read the readiness / compliance path can make.
311
+ // Every database-facing read the readiness path can make.
309
312
  let policyFindOneById: jest.SpyInstance;
310
313
  let escalationUserFindBy: jest.SpyInstance;
311
314
  let escalationTeamFindBy: jest.SpyInstance;
@@ -327,9 +330,6 @@ let notificationRuleFindBy: jest.SpyInstance;
327
330
  let incidentSeverityFindBy: jest.SpyInstance;
328
331
  let alertSeverityFindBy: jest.SpyInstance;
329
332
  let projectFindOneById: jest.SpyInstance;
330
- let teamFindOneById: jest.SpyInstance;
331
- let teamFindOneBy: jest.SpyInstance;
332
- let complianceSettingFindBy: jest.SpyInstance;
333
333
 
334
334
  function everyFindBySpy(): Array<jest.SpyInstance> {
335
335
  return [
@@ -352,7 +352,6 @@ function everyFindBySpy(): Array<jest.SpyInstance> {
352
352
  notificationRuleFindBy,
353
353
  incidentSeverityFindBy,
354
354
  alertSeverityFindBy,
355
- complianceSettingFindBy,
356
355
  ];
357
356
  }
358
357
 
@@ -361,20 +360,6 @@ let otherProjectId: ObjectID;
361
360
  let callerUserId: ObjectID;
362
361
  let policyId: ObjectID;
363
362
  let subjectUserId: ObjectID;
364
- let teamId: ObjectID;
365
-
366
- beforeAll(() => {
367
- /*
368
- * OnCallReadinessAPI registers its routes when the MODULE is evaluated - it
369
- * exports a bare router rather than a class - so its three routes are already
370
- * on mockRouter by the time this runs. TeamComplianceAPI registers in its
371
- * constructor, so it needs the explicit `new`. Nothing clears
372
- * mockRouter.routes here for exactly that reason: doing so would throw away
373
- * the readiness routes, which cannot be re-registered without re-importing
374
- * the module.
375
- */
376
- new TeamComplianceAPI();
377
- });
378
363
 
379
364
  beforeEach(() => {
380
365
  jest.clearAllMocks();
@@ -393,7 +378,6 @@ beforeEach(() => {
393
378
  callerUserId = ObjectID.generate();
394
379
  policyId = ObjectID.generate();
395
380
  subjectUserId = ObjectID.generate();
396
- teamId = ObjectID.generate();
397
381
  stubEmailMethodId = ObjectID.generate();
398
382
  stubTeamId = ObjectID.generate();
399
383
 
@@ -481,30 +465,6 @@ beforeEach(() => {
481
465
  enableWhatsAppNotifications: true,
482
466
  enableTelegramNotifications: true,
483
467
  } as never);
484
-
485
- /*
486
- * The team is read TWICE on the compliance route, by two different methods,
487
- * and both are stubbed because they answer two different questions.
488
- *
489
- * findOneById is the route's authorisation read: it fetches the team's own
490
- * projectId so the handler can refuse a team that belongs to somebody else.
491
- * The stub therefore carries a projectId - a row without one is refused, which
492
- * is the behaviour the "foreign team" tests below rely on.
493
- *
494
- * findOneBy is the service's own read, scoped to id AND project in the query,
495
- * so that an in-process caller reaching the service directly cannot resolve a
496
- * foreign team either.
497
- */
498
- teamFindOneById = jest.spyOn(TeamService, "findOneById").mockResolvedValue({
499
- _id: teamId.toString(),
500
- projectId: projectId,
501
- } as never);
502
- teamFindOneBy = jest
503
- .spyOn(TeamService, "findOneBy")
504
- .mockResolvedValue({ name: "Platform On-Call" } as never);
505
- complianceSettingFindBy = jest
506
- .spyOn(TeamComplianceSettingService, "findBy")
507
- .mockResolvedValue([] as never);
508
468
  });
509
469
 
510
470
  afterEach(() => {
@@ -910,7 +870,12 @@ describe("GET /on-call-readiness/policy/:policyId", () => {
910
870
  expect(read.props.isRoot).toBe(true);
911
871
  });
912
872
 
913
- test("refuses an unauthenticated caller before reading anything", async () => {
873
+ /*
874
+ * No session and no tenant header. This was a BadDataException (400,
875
+ * "Project ID is required") until the guards learned to ask "who are you?"
876
+ * before "which project?"; a 400 never made the dashboard refresh.
877
+ */
878
+ test("refuses an unauthenticated caller with 401 before reading anything", async () => {
914
879
  propsSpy.mockResolvedValue({} as never);
915
880
 
916
881
  const result: RouteCallResult = await callGetRoute({
@@ -918,17 +883,21 @@ describe("GET /on-call-readiness/policy/:policyId", () => {
918
883
  params: { policyId: policyId.toString() },
919
884
  });
920
885
 
921
- expect(result.thrownToNext).toBeInstanceOf(BadDataException);
886
+ expectAuthenticationRequired(result.thrownToNext);
922
887
  expect(policyFindOneById).not.toHaveBeenCalled();
923
888
  expect(policySpy).not.toHaveBeenCalled();
924
889
  });
925
890
 
926
- test("refuses a public caller that merely supplies a tenantid header", async () => {
891
+ test("refuses a public caller that merely supplies a tenantid header with 401", async () => {
927
892
  /*
928
893
  * The exact shape getUserMiddleware produces for an anonymous request that
929
894
  * carried a `tenantid` header: a project id, no user, no permissions. If
930
895
  * this got through, one header would be the whole authentication story for
931
896
  * every responder's configuration in that project.
897
+ *
898
+ * It is also the shape of an admin's request once their access-token
899
+ * cookie has expired, which is why the refusal is a 401 (it used to be the
900
+ * 422 "not authorized") - the dashboard refreshes and resends on a 401.
932
901
  */
933
902
  propsSpy.mockResolvedValue({
934
903
  tenantId: projectId,
@@ -941,11 +910,43 @@ describe("GET /on-call-readiness/policy/:policyId", () => {
941
910
  params: { policyId: policyId.toString() },
942
911
  });
943
912
 
944
- expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
913
+ expectAuthenticationRequired(result.thrownToNext);
945
914
  expect(policyFindOneById).not.toHaveBeenCalled();
946
915
  expect(policySpy).not.toHaveBeenCalled();
947
916
  });
948
917
 
918
+ test("an anonymous caller is refused before the path is validated", async () => {
919
+ /*
920
+ * A malformed id from a logged-in member is a 400 (below). From a caller
921
+ * with no credentials it must still be the 401, so the refresh-and-replay
922
+ * happens and the replayed request gets the real answer.
923
+ */
924
+ propsSpy.mockResolvedValue({ tenantId: projectId } as never);
925
+
926
+ const result: RouteCallResult = await callGetRoute({
927
+ uri: POLICY_ROUTE,
928
+ params: { policyId: "not-a-uuid" },
929
+ });
930
+
931
+ expectAuthenticationRequired(result.thrownToNext);
932
+ expect(policyFindOneById).not.toHaveBeenCalled();
933
+ });
934
+
935
+ test("a logged-in caller with no tenant header is still the 400", async () => {
936
+ propsSpy.mockResolvedValue({ userId: callerUserId } as never);
937
+
938
+ const result: RouteCallResult = await callGetRoute({
939
+ uri: POLICY_ROUTE,
940
+ params: { policyId: policyId.toString() },
941
+ });
942
+
943
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
944
+ expect((result.thrownToNext as Exception).code).toBe(
945
+ ExceptionCode.BadDataException,
946
+ );
947
+ expect(policyFindOneById).not.toHaveBeenCalled();
948
+ });
949
+
949
950
  test("a member of one project cannot read another project's policy", async () => {
950
951
  policyFindOneById.mockResolvedValue({
951
952
  id: policyId,
@@ -1057,12 +1058,36 @@ describe("GET /on-call-readiness/project", () => {
1057
1058
  expect(projectSpy.mock.calls[0]).toEqual([projectId]);
1058
1059
  });
1059
1060
 
1060
- test("refuses an unauthenticated caller", async () => {
1061
+ // Was a BadDataException (400) before the credential check.
1062
+ test("refuses an unauthenticated caller with 401", async () => {
1061
1063
  propsSpy.mockResolvedValue({} as never);
1062
1064
 
1063
1065
  const result: RouteCallResult = await callGetRoute({ uri: PROJECT_ROUTE });
1064
1066
 
1065
- expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1067
+ expectAuthenticationRequired(result.thrownToNext);
1068
+ expect(projectSpy).not.toHaveBeenCalled();
1069
+ });
1070
+
1071
+ test("refuses an anonymous caller carrying a tenantid header with 401, and does not clear the cache for it", async () => {
1072
+ /*
1073
+ * ?refresh=true drops the readiness cache for everyone. It sits after the
1074
+ * guard, so a caller with no session cannot use it to make every other
1075
+ * viewer's next page load recompute.
1076
+ */
1077
+ const clearSpy: jest.SpyInstance = jest.spyOn(
1078
+ OnCallReadinessService,
1079
+ "clearCache",
1080
+ );
1081
+
1082
+ propsSpy.mockResolvedValue({ tenantId: projectId } as never);
1083
+
1084
+ const result: RouteCallResult = await callGetRoute({
1085
+ uri: PROJECT_ROUTE,
1086
+ query: { refresh: "true" },
1087
+ });
1088
+
1089
+ expectAuthenticationRequired(result.thrownToNext);
1090
+ expect(clearSpy).not.toHaveBeenCalled();
1066
1091
  expect(projectSpy).not.toHaveBeenCalled();
1067
1092
  });
1068
1093
 
@@ -1261,7 +1286,12 @@ describe("paging the readiness summaries", () => {
1261
1286
  expect(payload["skip"]).toBe(200);
1262
1287
  });
1263
1288
 
1264
- test("paging is refused for a caller who is not a member, before any readiness work", async () => {
1289
+ /*
1290
+ * The caller here has no session at all, so the refusal is the 401 (it was
1291
+ * the 422 before the credential check). The point of the test is unchanged:
1292
+ * the paging parameters buy nothing before authorisation.
1293
+ */
1294
+ test("paging is refused for a caller with no session (401), before any readiness work", async () => {
1265
1295
  propsSpy.mockResolvedValue({
1266
1296
  tenantId: projectId,
1267
1297
  userId: undefined,
@@ -1273,7 +1303,26 @@ describe("paging the readiness summaries", () => {
1273
1303
  query: { limit: "10" },
1274
1304
  });
1275
1305
 
1306
+ expectAuthenticationRequired(result.thrownToNext);
1307
+ expect(projectSpy).not.toHaveBeenCalled();
1308
+ });
1309
+
1310
+ test("paging is refused for a logged-in caller who is not a member (422), before any readiness work", async () => {
1311
+ propsSpy.mockResolvedValue({
1312
+ tenantId: projectId,
1313
+ userId: callerUserId,
1314
+ userTenantAccessPermission: {},
1315
+ } as never);
1316
+
1317
+ const result: RouteCallResult = await callGetRoute({
1318
+ uri: PROJECT_ROUTE,
1319
+ query: { limit: "10" },
1320
+ });
1321
+
1276
1322
  expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
1323
+ expect((result.thrownToNext as Exception).code).toBe(
1324
+ ExceptionCode.NotAuthorizedException,
1325
+ );
1277
1326
  expect(projectSpy).not.toHaveBeenCalled();
1278
1327
  });
1279
1328
  });
@@ -1393,7 +1442,8 @@ describe("GET /on-call-readiness/user/:userId", () => {
1393
1442
  expect(userSpy).not.toHaveBeenCalled();
1394
1443
  });
1395
1444
 
1396
- test("refuses an unauthenticated caller before the membership query", async () => {
1445
+ // Was a BadDataException (400) before the credential check.
1446
+ test("refuses an unauthenticated caller with 401 before the membership query", async () => {
1397
1447
  propsSpy.mockResolvedValue({} as never);
1398
1448
 
1399
1449
  const result: RouteCallResult = await callGetRoute({
@@ -1401,12 +1451,13 @@ describe("GET /on-call-readiness/user/:userId", () => {
1401
1451
  params: { userId: subjectUserId.toString() },
1402
1452
  });
1403
1453
 
1404
- expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1454
+ expectAuthenticationRequired(result.thrownToNext);
1405
1455
  expect(teamMemberFindBy).not.toHaveBeenCalled();
1406
1456
  expect(userSpy).not.toHaveBeenCalled();
1407
1457
  });
1408
1458
 
1409
- test("refuses a public caller that merely supplies a tenantid header", async () => {
1459
+ // Was a NotAuthorizedException (422) before the credential check.
1460
+ test("refuses a public caller that merely supplies a tenantid header with 401", async () => {
1410
1461
  propsSpy.mockResolvedValue({
1411
1462
  tenantId: projectId,
1412
1463
  userId: undefined,
@@ -1418,7 +1469,37 @@ describe("GET /on-call-readiness/user/:userId", () => {
1418
1469
  params: { userId: subjectUserId.toString() },
1419
1470
  });
1420
1471
 
1472
+ expectAuthenticationRequired(result.thrownToNext);
1473
+ expect(teamMemberFindBy).not.toHaveBeenCalled();
1474
+ expect(userSpy).not.toHaveBeenCalled();
1475
+ });
1476
+
1477
+ test("a project API key is authenticated but not a member: still the 422", async () => {
1478
+ /*
1479
+ * The readiness routes are for people inside the project. A key is a
1480
+ * credential, so it must NOT get the 401 - an API client would answer it
1481
+ * by trying to refresh a session it never had.
1482
+ */
1483
+ propsSpy.mockResolvedValue({
1484
+ tenantId: projectId,
1485
+ userId: undefined,
1486
+ userType: UserType.API,
1487
+ userTenantAccessPermission: buildMemberProps({
1488
+ projectId: projectId,
1489
+ userId: callerUserId,
1490
+ }).userTenantAccessPermission,
1491
+ } as never);
1492
+
1493
+ const result: RouteCallResult = await callGetRoute({
1494
+ uri: USER_ROUTE,
1495
+ params: { userId: subjectUserId.toString() },
1496
+ });
1497
+
1421
1498
  expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
1499
+ expect((result.thrownToNext as Exception).code).toBe(
1500
+ ExceptionCode.NotAuthorizedException,
1501
+ );
1502
+ expect((result.thrownToNext as Exception).message).toBe(REFUSAL);
1422
1503
  expect(teamMemberFindBy).not.toHaveBeenCalled();
1423
1504
  expect(userSpy).not.toHaveBeenCalled();
1424
1505
  });
@@ -1970,830 +2051,33 @@ describe("GET /on-call-readiness/user/:userId - identifier masking", () => {
1970
2051
 
1971
2052
  /*
1972
2053
  * --------------------------------------------------------------------------- *
1973
- * The TeamComplianceService rebuild, exercised through its own route with the
1974
- * REAL readiness service underneath.
1975
- *
1976
- * Teams > View > Compliance renders this payload field for field - including
1977
- * the reason strings, which it prints as prose rather than mapping through any
1978
- * lookup - so the shape is a hard contract. What changed underneath is where
1979
- * the two "does this user have on-call rules?" answers come from, and the four
1980
- * defects that lived in the old answer are re-tested here across the real seam
1981
- * rather than against a stubbed readiness service.
2054
+ * A refused caller costs nothing. Every database-facing read the readiness
2055
+ * routes can reach is stubbed above; an anonymous caller must be turned away
2056
+ * before ANY of them runs, on every read route - the refusal is the only gate,
2057
+ * so it has to come first.
1982
2058
  * ---------------------------------------------------------------------------
1983
2059
  */
1984
2060
 
1985
- interface StubRule {
1986
- _id: string;
1987
- userId: ObjectID;
1988
- ruleType: NotificationRuleType;
1989
- incidentSeverityId?: ObjectID | undefined;
1990
- alertSeverityId?: ObjectID | undefined;
1991
- isOptOut?: boolean | undefined;
1992
- userCallId?: ObjectID | undefined;
1993
- userSmsId?: ObjectID | undefined;
1994
- userEmailId?: ObjectID | undefined;
1995
- userPushId?: ObjectID | undefined;
1996
- userTelegramId?: ObjectID | undefined;
1997
- userSlackId?: ObjectID | undefined;
1998
- userMicrosoftTeamsId?: ObjectID | undefined;
1999
- userWhatsAppId?: ObjectID | undefined;
2000
- userWebhookId?: ObjectID | undefined;
2001
- }
2002
-
2003
- describe("GET /team/compliance-status/:teamId - the rebuilt service", () => {
2004
- let ada: StubUser;
2005
- let grace: StubUser;
2006
- let critical: StubSeverity;
2007
- let major: StubSeverity;
2008
- let page: StubSeverity;
2009
-
2010
- function stage(data: {
2011
- settings: Array<{ ruleType: ComplianceRuleType; enabled: boolean }>;
2012
- members: Array<StubUser>;
2013
- rules: Array<StubRule>;
2014
- incidentSeverities: Array<StubSeverity>;
2015
- alertSeverities: Array<StubSeverity>;
2016
- }): void {
2017
- complianceSettingFindBy.mockResolvedValue(data.settings as never);
2018
-
2019
- teamMemberFindBy.mockResolvedValue(
2020
- data.members.map((member: StubUser): Record<string, unknown> => {
2021
- return { _id: `tm-${member.id.toString()}`, userId: member.id };
2022
- }) as never,
2023
- );
2024
-
2025
- userFindBy.mockResolvedValue(data.members as never);
2026
-
2027
- /*
2028
- * The members are attached DIRECTLY to an escalation rule, which is the
2029
- * arrangement that lets the project-scope readiness summary answer for all
2030
- * of them in one pass. It is also the arrangement TeamComplianceService is
2031
- * designed around: a team on a compliance page exists because its members
2032
- * are on call.
2033
- */
2034
- escalationUserFindBy.mockResolvedValue(
2035
- data.members.map((member: StubUser): Record<string, unknown> => {
2036
- return { _id: `er-${member.id.toString()}`, userId: member.id };
2037
- }) as never,
2038
- );
2039
-
2040
- notificationRuleFindBy.mockResolvedValue(data.rules as never);
2041
- incidentSeverityFindBy.mockResolvedValue(data.incidentSeverities as never);
2042
- alertSeverityFindBy.mockResolvedValue(data.alertSeverities as never);
2043
- }
2044
-
2045
- async function readCompliance(): Promise<Record<string, unknown>> {
2046
- const result: RouteCallResult = await callGetRoute({
2047
- uri: COMPLIANCE_ROUTE,
2048
- params: { teamId: teamId.toString() },
2049
- });
2050
-
2051
- expect(result.nextCallCount).toBe(0);
2052
-
2053
- return jsonPayload();
2054
- }
2055
-
2056
- function statusesOf(
2057
- payload: Record<string, unknown>,
2058
- ): Array<Record<string, unknown>> {
2059
- return payload["userComplianceStatuses"] as Array<Record<string, unknown>>;
2060
- }
2061
-
2062
- beforeEach(() => {
2063
- ada = { id: ObjectID.generate(), name: "Ada", email: "ada@example.com" };
2064
- grace = {
2065
- id: ObjectID.generate(),
2066
- name: "Grace",
2067
- email: "grace@example.com",
2068
- };
2069
- critical = { id: ObjectID.generate(), name: "Critical" };
2070
- major = { id: ObjectID.generate(), name: "Major" };
2071
- page = { id: ObjectID.generate(), name: "Page" };
2072
- });
2073
-
2074
- test("the response shape the dashboard renders is unchanged", async () => {
2075
- stage({
2076
- settings: [
2077
- {
2078
- ruleType: ComplianceRuleType.HasNotificationEmailMethod,
2079
- enabled: true,
2080
- },
2081
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2082
- ],
2083
- members: [ada],
2084
- rules: [],
2085
- incidentSeverities: [critical],
2086
- alertSeverities: [],
2087
- });
2088
-
2089
- const payload: Record<string, unknown> = await readCompliance();
2090
-
2091
- expect(Object.keys(payload).sort()).toEqual([
2092
- "complianceSettings",
2093
- "teamId",
2094
- "teamName",
2095
- "userComplianceStatuses",
2096
- ]);
2097
- expect(payload["teamId"]).toBe(teamId.toString());
2098
- expect(payload["teamName"]).toBe("Platform On-Call");
2099
- expect(payload["complianceSettings"]).toEqual([
2100
- {
2101
- ruleType: ComplianceRuleType.HasNotificationEmailMethod,
2102
- enabled: true,
2103
- },
2104
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2105
- ]);
2106
-
2107
- const statuses: Array<Record<string, unknown>> = statusesOf(payload);
2108
- expect(statuses).toHaveLength(1);
2109
- expect(Object.keys(statuses[0]!).sort()).toEqual([
2110
- "isCompliant",
2111
- "nonCompliantRules",
2112
- "userEmail",
2113
- "userId",
2114
- "userName",
2115
- "userProfilePictureId",
2116
- ]);
2117
- expect(statuses[0]!["userId"]).toBe(ada.id.toString());
2118
- expect(statuses[0]!["userName"]).toBe("Ada");
2119
- expect(statuses[0]!["isCompliant"]).toBe(false);
2120
- /*
2121
- * The reason strings are printed verbatim by TeamComplianceStatusTable, so
2122
- * their exact wording is part of the payload contract, not an internal
2123
- * detail. Both an old-style channel rule and a rebuilt on-call rule are
2124
- * asserted together to show neither vocabulary shifted.
2125
- */
2126
- expect(statuses[0]!["nonCompliantRules"]).toEqual([
2127
- {
2128
- ruleType: ComplianceRuleType.HasNotificationEmailMethod,
2129
- reason: "No verified email address configured for notifications",
2130
- },
2131
- {
2132
- ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2133
- reason: "Missing notification rules for incident severities: Critical",
2134
- },
2135
- ]);
2136
- });
2137
-
2138
- test("DEFECT closed: a rule on Telegram, WhatsApp, Slack, Teams or a webhook now counts", async () => {
2139
- /*
2140
- * The old check read userCallId/userSmsId/userEmailId/userPushId off the
2141
- * rule row and treated a row carrying none of them as no rule at all, so a
2142
- * responder reachable only on Telegram, WhatsApp or a webhook was reported
2143
- * non-compliant while the runtime was quite happily paging them. A false RED
2144
- * teaches admins to ignore the table, which is worse than no table. Slack
2145
- * and Microsoft Teams arrived after the fix and are staged alongside so the
2146
- * defect cannot return for the channels that never lived through it.
2147
- *
2148
- * All five channels are staged onto one user at once: whichever column the
2149
- * rule carries, the row is a rule.
2150
- */
2151
- const minor: StubSeverity = { id: ObjectID.generate(), name: "Minor" };
2152
- const warn: StubSeverity = { id: ObjectID.generate(), name: "Warn" };
2153
-
2154
- stage({
2155
- settings: [
2156
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2157
- { ruleType: ComplianceRuleType.HasAlertOnCallRules, enabled: true },
2158
- ],
2159
- members: [ada],
2160
- rules: [
2161
- {
2162
- _id: "rule-telegram",
2163
- userId: ada.id,
2164
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2165
- incidentSeverityId: critical.id,
2166
- userTelegramId: ObjectID.generate(),
2167
- },
2168
- {
2169
- _id: "rule-whatsapp",
2170
- userId: ada.id,
2171
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2172
- incidentSeverityId: major.id,
2173
- userWhatsAppId: ObjectID.generate(),
2174
- },
2175
- {
2176
- _id: "rule-slack",
2177
- userId: ada.id,
2178
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2179
- incidentSeverityId: minor.id,
2180
- userSlackId: ObjectID.generate(),
2181
- },
2182
- {
2183
- _id: "rule-webhook",
2184
- userId: ada.id,
2185
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
2186
- alertSeverityId: page.id,
2187
- userWebhookId: ObjectID.generate(),
2188
- },
2189
- {
2190
- _id: "rule-microsoft-teams",
2191
- userId: ada.id,
2192
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
2193
- alertSeverityId: warn.id,
2194
- userMicrosoftTeamsId: ObjectID.generate(),
2195
- },
2196
- ],
2197
- incidentSeverities: [critical, major, minor],
2198
- alertSeverities: [page, warn],
2199
- });
2200
-
2201
- const statuses: Array<Record<string, unknown>> = statusesOf(
2202
- await readCompliance(),
2203
- );
2204
-
2205
- expect(statuses[0]!["isCompliant"]).toBe(true);
2206
- expect(statuses[0]!["nonCompliantRules"]).toEqual([]);
2207
- });
2208
-
2209
- test("DEFECT closed: no channel column is even read, so none can be missed", async () => {
2210
- /*
2211
- * The structural half of the fix. The three formerly-invisible channels were
2212
- * invisible because they were never SELECTed; asserting that NONE of the
2213
- * nine is selected means no future edit can reintroduce a partial column
2214
- * list and quietly start under-counting again.
2215
- */
2216
- stage({
2217
- settings: [
2218
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2219
- ],
2220
- members: [ada],
2221
- rules: [],
2222
- incidentSeverities: [critical],
2223
- alertSeverities: [],
2224
- });
2225
-
2226
- await readCompliance();
2227
-
2228
- const call: CapturedFindBy = firstCall(notificationRuleFindBy);
2229
-
2230
- for (const column of [
2231
- "userCallId",
2232
- "userSmsId",
2233
- "userEmailId",
2234
- "userPushId",
2235
- "userTelegramId",
2236
- "userSlackId",
2237
- "userMicrosoftTeamsId",
2238
- "userWhatsAppId",
2239
- "userWebhookId",
2240
- ]) {
2241
- expect(call.select?.[column]).toBeUndefined();
2242
- }
2243
-
2244
- // What IS read: who, what kind of page, which severity, and opt-out state.
2245
- expect(call.select?.["ruleType"]).toBe(true);
2246
- expect(call.select?.["incidentSeverityId"]).toBe(true);
2247
- expect(call.select?.["alertSeverityId"]).toBe(true);
2248
- expect(call.select?.["isOptOut"]).toBe(true);
2249
- });
2250
-
2251
- test("DEFECT closed: a WHEN_USER_GOES_OFF_CALL rule is no longer incident coverage", async () => {
2252
- /*
2253
- * The false GREEN, and the more dangerous of the two directions: the owner
2254
- * was told a responder was covered for Sev1 incidents when their only rule
2255
- * fired as they went off call. The row below carries an incidentSeverityId,
2256
- * which is exactly what made the old severity-only match accept it.
2257
- */
2258
- stage({
2259
- settings: [
2260
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2261
- ],
2262
- members: [ada],
2263
- rules: [
2264
- {
2265
- _id: "rule-off-call",
2266
- userId: ada.id,
2267
- ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
2268
- incidentSeverityId: critical.id,
2269
- userEmailId: ObjectID.generate(),
2270
- },
2271
- ],
2272
- incidentSeverities: [critical],
2273
- alertSeverities: [],
2274
- });
2275
-
2276
- const statuses: Array<Record<string, unknown>> = statusesOf(
2277
- await readCompliance(),
2278
- );
2279
-
2280
- expect(statuses[0]!["isCompliant"]).toBe(false);
2281
- expect(statuses[0]!["nonCompliantRules"]).toEqual([
2282
- {
2283
- ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2284
- reason: "Missing notification rules for incident severities: Critical",
2285
- },
2286
- ]);
2287
- });
2288
-
2289
- test("DEFECT closed: an alert rule does not satisfy an incident severity of the same id", async () => {
2290
- /*
2291
- * The sharpest form of the ruleType fix. One rule row, one severity id, and
2292
- * the two checks disagree about it - because the severity is taken from the
2293
- * column the RULE TYPE dictates rather than from whichever one happens to be
2294
- * populated.
2295
- */
2296
- stage({
2297
- settings: [
2298
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2299
- { ruleType: ComplianceRuleType.HasAlertOnCallRules, enabled: true },
2300
- ],
2301
- members: [ada],
2302
- rules: [
2303
- {
2304
- _id: "rule-alert-only",
2305
- userId: ada.id,
2306
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
2307
- alertSeverityId: page.id,
2308
- userEmailId: ObjectID.generate(),
2309
- },
2310
- ],
2311
- incidentSeverities: [critical],
2312
- alertSeverities: [page],
2313
- });
2314
-
2315
- const statuses: Array<Record<string, unknown>> = statusesOf(
2316
- await readCompliance(),
2317
- );
2318
-
2319
- expect(statuses[0]!["nonCompliantRules"]).toEqual([
2320
- {
2321
- ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2322
- reason: "Missing notification rules for incident severities: Critical",
2323
- },
2324
- ]);
2325
- });
2326
-
2327
- test("a legacy rule row with a NULL isOptOut still counts as coverage", async () => {
2328
- /*
2329
- * isOptOut is nullable and was added long after these rows started
2330
- * existing, so it is NULL on every rule in every existing install. An
2331
- * implementation that classified coverage with `isOptOut === false` would
2332
- * match none of them and report a fully-configured project as entirely
2333
- * unready - which is why the split is `isOptOut === true`, the exact dual of
2334
- * the notInOrNull predicate the paging path uses.
2335
- */
2336
- stage({
2337
- settings: [
2338
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2339
- ],
2340
- members: [ada],
2341
- rules: [
2342
- {
2343
- _id: "rule-legacy",
2344
- userId: ada.id,
2345
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2346
- incidentSeverityId: critical.id,
2347
- isOptOut: undefined,
2348
- userEmailId: ObjectID.generate(),
2349
- },
2350
- ],
2351
- incidentSeverities: [critical],
2352
- alertSeverities: [],
2353
- });
2354
-
2355
- const statuses: Array<Record<string, unknown>> = statusesOf(
2356
- await readCompliance(),
2357
- );
2358
-
2359
- expect(statuses[0]!["isCompliant"]).toBe(true);
2360
- });
2061
+ describe("the readiness read routes - a refused caller reads nothing", () => {
2062
+ test.each<[string]>([[POLICY_ROUTE], [PROJECT_ROUTE], [USER_ROUTE]])(
2063
+ "%s",
2064
+ async (uri: string) => {
2065
+ propsSpy.mockResolvedValue({} as never);
2361
2066
 
2362
- test("an explicitly opted-out severity is coverage, not a gap", async () => {
2363
- stage({
2364
- settings: [
2365
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2366
- ],
2367
- members: [ada],
2368
- rules: [
2369
- {
2370
- _id: "rule-opt-out",
2371
- userId: ada.id,
2372
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2373
- incidentSeverityId: critical.id,
2374
- isOptOut: true,
2067
+ const result: RouteCallResult = await callGetRoute({
2068
+ uri,
2069
+ params: {
2070
+ policyId: policyId.toString(),
2071
+ userId: subjectUserId.toString(),
2375
2072
  },
2376
- ],
2377
- incidentSeverities: [critical],
2378
- alertSeverities: [],
2379
- });
2380
-
2381
- const statuses: Array<Record<string, unknown>> = statusesOf(
2382
- await readCompliance(),
2383
- );
2384
-
2385
- // Deliberate silence is not a compliance failure.
2386
- expect(statuses[0]!["isCompliant"]).toBe(true);
2387
- });
2388
-
2389
- test("DEFECT closed: not one read in the whole render carries the literal limit 100", async () => {
2390
- /*
2391
- * The old code capped team members, users and alert severities at 100 while
2392
- * capping incident severities at LIMIT_PER_PROJECT. Truncation is the worst
2393
- * failure mode a compliance page has: the 101st member was not reported
2394
- * non-compliant, they were simply absent, and an absent row reads as "no
2395
- * problem here". Sweeping every read the render makes - the compliance
2396
- * service's three and the readiness service's dozen - is the assertion that
2397
- * cannot be satisfied by fixing three call sites and missing a fourth.
2398
- */
2399
- stage({
2400
- settings: [
2401
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2402
- { ruleType: ComplianceRuleType.HasAlertOnCallRules, enabled: true },
2403
- ],
2404
- members: [ada, grace],
2405
- rules: [],
2406
- incidentSeverities: [critical, major],
2407
- alertSeverities: [page],
2408
- });
2409
-
2410
- await readCompliance();
2073
+ });
2411
2074
 
2412
- let readsInspected: number = 0;
2075
+ expect(result.nextCallCount).toBe(1);
2076
+ expect(result.thrownToNext).toBeDefined();
2413
2077
 
2414
- for (const spy of everyFindBySpy()) {
2415
- for (const call of callsOf(spy)) {
2416
- readsInspected++;
2417
- expect(call.limit).not.toBe(100);
2078
+ for (const spy of [...everyFindBySpy(), projectFindOneById]) {
2079
+ expect(spy).not.toHaveBeenCalled();
2418
2080
  }
2419
- }
2420
-
2421
- // Guards the guard: an assertion over zero reads proves nothing.
2422
- expect(readsInspected).toBeGreaterThan(10);
2423
-
2424
- expect(firstCall(complianceSettingFindBy).limit).toBe(LIMIT_PER_PROJECT);
2425
- expect(firstCall(teamMemberFindBy).limit).toBe(LIMIT_PER_PROJECT);
2426
- expect(firstCall(userFindBy).limit).toBe(LIMIT_PER_PROJECT);
2427
- // Both severity kinds, which is where the two halves used to disagree.
2428
- expect(firstCall(incidentSeverityFindBy).limit).toBe(LIMIT_PER_PROJECT);
2429
- expect(firstCall(alertSeverityFindBy).limit).toBe(LIMIT_PER_PROJECT);
2430
- expect(firstCall(notificationRuleFindBy).limit).toBe(LIMIT_PER_PROJECT);
2431
- });
2432
-
2433
- test("DEFECT closed: the read count does not grow with members or severities", async () => {
2434
- /*
2435
- * The N+1. The old service issued one findBy per severity per user, so a
2436
- * team of 20 in a project with 5 severities cost 100 round trips to render
2437
- * one page. The proof is comparative rather than absolute: the same render
2438
- * with three times the members and three times the severities must cost the
2439
- * same number of reads, whatever that number happens to be.
2440
- */
2441
- stage({
2442
- settings: [
2443
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2444
- ],
2445
- members: [ada],
2446
- rules: [],
2447
- incidentSeverities: [critical],
2448
- alertSeverities: [],
2449
- });
2450
-
2451
- await readCompliance();
2452
-
2453
- const smallRuleReads: number = notificationRuleFindBy.mock.calls.length;
2454
- const smallUserReads: number = userFindBy.mock.calls.length;
2455
- const smallSeverityReads: number =
2456
- incidentSeverityFindBy.mock.calls.length +
2457
- alertSeverityFindBy.mock.calls.length;
2458
-
2459
- expect(smallRuleReads).toBe(1);
2460
-
2461
- /*
2462
- * mockClear, not mockReset: the stubs' resolved values are implementations
2463
- * and have to survive into the second render.
2464
- */
2465
- jest.clearAllMocks();
2466
- OnCallReadinessService.clearCache();
2467
-
2468
- const carol: StubUser = {
2469
- id: ObjectID.generate(),
2470
- name: "Carol",
2471
- email: "carol@example.com",
2472
- };
2473
- const minor: StubSeverity = { id: ObjectID.generate(), name: "Minor" };
2474
- const trivial: StubSeverity = { id: ObjectID.generate(), name: "Trivial" };
2475
-
2476
- stage({
2477
- settings: [
2478
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2479
- ],
2480
- members: [ada, grace, carol],
2481
- rules: [],
2482
- incidentSeverities: [critical, major, minor, trivial],
2483
- alertSeverities: [page],
2484
- });
2485
-
2486
- await readCompliance();
2487
-
2488
- expect(notificationRuleFindBy.mock.calls.length).toBe(smallRuleReads);
2489
- expect(userFindBy.mock.calls.length).toBe(smallUserReads);
2490
- expect(
2491
- incidentSeverityFindBy.mock.calls.length +
2492
- alertSeverityFindBy.mock.calls.length,
2493
- ).toBe(smallSeverityReads);
2494
- });
2495
-
2496
- test("one notification-rule read covers every member, batched on userId", async () => {
2497
- stage({
2498
- settings: [
2499
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: true },
2500
- ],
2501
- members: [ada, grace],
2502
- rules: [
2503
- {
2504
- _id: "rule-ada",
2505
- userId: ada.id,
2506
- ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2507
- incidentSeverityId: critical.id,
2508
- userEmailId: ObjectID.generate(),
2509
- },
2510
- ],
2511
- incidentSeverities: [critical],
2512
- alertSeverities: [],
2513
- });
2514
-
2515
- const statuses: Array<Record<string, unknown>> = statusesOf(
2516
- await readCompliance(),
2517
- );
2518
-
2519
- expect(notificationRuleFindBy).toHaveBeenCalledTimes(1);
2520
-
2521
- const call: CapturedFindBy = firstCall(notificationRuleFindBy);
2522
- // One query, scoped to the project, listing every user it is asking about.
2523
- expect(call.query["userId"]).toBeDefined();
2524
- expect(call.props?.isRoot).toBe(true);
2525
-
2526
- /*
2527
- * And the batched read still separates the two people: Ada's rule must not
2528
- * cover Grace. A batched query folded into a per-user map is exactly where
2529
- * that mistake would hide.
2530
- */
2531
- expect(statuses[0]!["userName"]).toBe("Ada");
2532
- expect(statuses[0]!["isCompliant"]).toBe(true);
2533
- expect(statuses[1]!["userName"]).toBe("Grace");
2534
- expect(statuses[1]!["isCompliant"]).toBe(false);
2535
- });
2536
-
2537
- test("readiness is not computed at all when no on-call rule is enabled", async () => {
2538
- /*
2539
- * The four channel rules do not consult readiness, so a team that only
2540
- * checks "has a verified email" must not pay for a project-wide readiness
2541
- * pass on every render.
2542
- */
2543
- stage({
2544
- settings: [
2545
- {
2546
- ruleType: ComplianceRuleType.HasNotificationEmailMethod,
2547
- enabled: true,
2548
- },
2549
- { ruleType: ComplianceRuleType.HasIncidentOnCallRules, enabled: false },
2550
- ],
2551
- members: [ada],
2552
- rules: [],
2553
- incidentSeverities: [critical],
2554
- alertSeverities: [],
2555
- });
2556
-
2557
- await readCompliance();
2558
-
2559
- expect(notificationRuleFindBy).not.toHaveBeenCalled();
2560
- expect(incidentSeverityFindBy).not.toHaveBeenCalled();
2561
- expect(escalationUserFindBy).not.toHaveBeenCalled();
2562
- expect(projectFindOneById).not.toHaveBeenCalled();
2563
- // The channel rule it WAS asked about still runs.
2564
- expect(userEmailFindBy).toHaveBeenCalledTimes(1);
2565
- });
2566
-
2567
- test("a team that does not exist is refused rather than described", async () => {
2568
- /*
2569
- * Refused by the ROUTE now, before the service is reached, and with the same
2570
- * words a team in another project gets - see the authorisation block below
2571
- * for why those two cases must be indistinguishable. The service refuses it
2572
- * as well, on its own, with a BadDataException; that guard is pinned in
2573
- * TeamComplianceServiceBehaviour.test.ts because it protects in-process
2574
- * callers who never come through this route.
2575
- */
2576
- teamFindOneById.mockResolvedValue(null as never);
2577
-
2578
- const result: RouteCallResult = await callGetRoute({
2579
- uri: COMPLIANCE_ROUTE,
2580
- params: { teamId: teamId.toString() },
2581
- });
2582
-
2583
- expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2584
- expect((result.thrownToNext as NotAuthorizedException).message).toBe(
2585
- REFUSAL,
2586
- );
2587
- expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
2588
- });
2589
- });
2590
-
2591
- /*
2592
- * --------------------------------------------------------------------------- *
2593
- * GET /team/compliance-status/:teamId - authorisation.
2594
- *
2595
- * PRE-EXISTING HOLE, not a Phase 2 regression, closed here because Phase 2
2596
- * rebuilt what sits behind this route.
2597
- *
2598
- * The route was mounted with UserMiddleware.getUserMiddleware - which admits an
2599
- * anonymous request as UserType.Public and calls next() - and then checked only
2600
- * that `databaseProps.tenantId` was non-empty. tenantId comes from a
2601
- * caller-supplied `tenantid` header. Everything underneath reads with
2602
- * isRoot: true. So a `tenantid` header and a team id were the entire
2603
- * authentication story for "which of these named people cannot be paged", which
2604
- * is a roster of who to phone during an outage and who will never pick up.
2605
- *
2606
- * Worse in combination: the service read the team by id with no projectId in the
2607
- * query, so the team did not even have to belong to the project in the header.
2608
- *
2609
- * Both halves are tested here - the caller has to be a member, AND the team has
2610
- * to be theirs - and the refusals are asserted to be word-for-word identical so
2611
- * the route cannot be used to enumerate team ids across tenants.
2612
- * ---------------------------------------------------------------------------
2613
- */
2614
-
2615
- describe("GET /team/compliance-status/:teamId - authorisation", () => {
2616
- let complianceSpy: jest.SpyInstance;
2617
-
2618
- beforeEach(() => {
2619
- complianceSpy = jest.spyOn(
2620
- TeamComplianceService,
2621
- "getTeamComplianceStatus",
2622
- );
2623
- });
2624
-
2625
- test("refuses an unauthenticated caller before reading anything", async () => {
2626
- propsSpy.mockResolvedValue({} as never);
2627
-
2628
- const result: RouteCallResult = await callGetRoute({
2629
- uri: COMPLIANCE_ROUTE,
2630
- params: { teamId: teamId.toString() },
2631
- });
2632
-
2633
- expect(result.thrownToNext).toBeInstanceOf(BadDataException);
2634
- expect(teamFindOneById).not.toHaveBeenCalled();
2635
- expect(complianceSpy).not.toHaveBeenCalled();
2636
- });
2637
-
2638
- test("refuses a public caller that merely supplies a tenantid header", async () => {
2639
- /*
2640
- * THE hole, in its exact shape: the request getUserMiddleware produces for
2641
- * an anonymous caller who sent a `tenantid` header. A project id, no user,
2642
- * no permissions - and, until this change, a complete compliance report.
2643
- */
2644
- propsSpy.mockResolvedValue({
2645
- tenantId: projectId,
2646
- userId: undefined,
2647
- userTenantAccessPermission: undefined,
2648
- } as never);
2649
-
2650
- const result: RouteCallResult = await callGetRoute({
2651
- uri: COMPLIANCE_ROUTE,
2652
- params: { teamId: teamId.toString() },
2653
- });
2654
-
2655
- expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2656
- expect((result.thrownToNext as NotAuthorizedException).message).toBe(
2657
- REFUSAL,
2658
- );
2659
- expect(teamFindOneById).not.toHaveBeenCalled();
2660
- expect(complianceSpy).not.toHaveBeenCalled();
2661
- });
2662
-
2663
- test("refuses a logged-in caller whose tenantid names a project they are not in", async () => {
2664
- propsSpy.mockResolvedValue({
2665
- tenantId: projectId,
2666
- userId: callerUserId,
2667
- // Logged in, but with no permission entry for the project they named.
2668
- userTenantAccessPermission: {},
2669
- } as never);
2670
-
2671
- const result: RouteCallResult = await callGetRoute({
2672
- uri: COMPLIANCE_ROUTE,
2673
- params: { teamId: teamId.toString() },
2674
- });
2675
-
2676
- expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2677
- expect(complianceSpy).not.toHaveBeenCalled();
2678
- });
2679
-
2680
- test("a member of one project cannot read another project's team", async () => {
2681
- /*
2682
- * The caller is a real member of their own project; only the team id is
2683
- * borrowed. Without the resource check this is the whole exploit - every
2684
- * read underneath runs as root, so the borrowed id resolves and the report
2685
- * is rendered for a team the caller has nothing to do with.
2686
- */
2687
- teamFindOneById.mockResolvedValue({
2688
- _id: teamId.toString(),
2689
- projectId: otherProjectId,
2690
- } as never);
2691
-
2692
- const result: RouteCallResult = await callGetRoute({
2693
- uri: COMPLIANCE_ROUTE,
2694
- params: { teamId: teamId.toString() },
2695
- });
2696
-
2697
- expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2698
- expect((result.thrownToNext as NotAuthorizedException).message).toBe(
2699
- REFUSAL,
2700
- );
2701
- expect(complianceSpy).not.toHaveBeenCalled();
2702
- expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
2703
- });
2704
-
2705
- test("a foreign team and an unknown team are refused in identical words", async () => {
2706
- teamFindOneById.mockResolvedValue({
2707
- _id: teamId.toString(),
2708
- projectId: otherProjectId,
2709
- } as never);
2710
- const foreign: RouteCallResult = await callGetRoute({
2711
- uri: COMPLIANCE_ROUTE,
2712
- params: { teamId: teamId.toString() },
2713
- });
2714
-
2715
- teamFindOneById.mockResolvedValue(null as never);
2716
- const unknown: RouteCallResult = await callGetRoute({
2717
- uri: COMPLIANCE_ROUTE,
2718
- params: { teamId: ObjectID.generate().toString() },
2719
- });
2720
-
2721
- expect((foreign.thrownToNext as NotAuthorizedException).message).toBe(
2722
- (unknown.thrownToNext as NotAuthorizedException).message,
2723
- );
2724
- expect((foreign.thrownToNext as NotAuthorizedException).message).toBe(
2725
- REFUSAL,
2726
- );
2727
- });
2728
-
2729
- test("a team row carrying no projectId at all is refused, not trusted", async () => {
2730
- // The shape a `select` that forgot projectId would produce.
2731
- teamFindOneById.mockResolvedValue({ _id: teamId.toString() } as never);
2732
-
2733
- const result: RouteCallResult = await callGetRoute({
2734
- uri: COMPLIANCE_ROUTE,
2735
- params: { teamId: teamId.toString() },
2736
- });
2737
-
2738
- expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2739
- expect(complianceSpy).not.toHaveBeenCalled();
2740
- });
2741
-
2742
- test("a malformed team id is rejected as bad data, not turned into a query", async () => {
2743
- /*
2744
- * The old handler built `new ObjectID(req.params.teamId)` from any string
2745
- * and then tested it for truthiness - which an ObjectID always is - so a
2746
- * malformed id travelled all the way to a query that matched nothing and
2747
- * came back as "this team does not exist".
2748
- */
2749
- const result: RouteCallResult = await callGetRoute({
2750
- uri: COMPLIANCE_ROUTE,
2751
- params: { teamId: "not-a-uuid" },
2752
- });
2753
-
2754
- expect(result.thrownToNext).toBeInstanceOf(BadDataException);
2755
- expect(teamFindOneById).not.toHaveBeenCalled();
2756
- expect(complianceSpy).not.toHaveBeenCalled();
2757
- });
2758
-
2759
- test("the authorised path reads the team's own projectId, as root, and proceeds", async () => {
2760
- const result: RouteCallResult = await callGetRoute({
2761
- uri: COMPLIANCE_ROUTE,
2762
- params: { teamId: teamId.toString() },
2763
- });
2764
-
2765
- expect(result.nextCallCount).toBe(0);
2766
-
2767
- const read: {
2768
- id: ObjectID;
2769
- select: Record<string, unknown>;
2770
- props: { isRoot?: boolean | undefined };
2771
- } = teamFindOneById.mock.calls[0]![0] as {
2772
- id: ObjectID;
2773
- select: Record<string, unknown>;
2774
- props: { isRoot?: boolean | undefined };
2775
- };
2776
-
2777
- expect(read.id.toString()).toBe(teamId.toString());
2778
- expect(read.select["projectId"]).toBe(true);
2779
- expect(read.props.isRoot).toBe(true);
2780
-
2781
- // And the service is asked about the project the CALLER was authorised for.
2782
- expect(complianceSpy).toHaveBeenCalledTimes(1);
2783
- expect(complianceSpy.mock.calls[0]![1]).toEqual(projectId);
2784
-
2785
- /*
2786
- * Underneath, the service scopes its OWN team read to id and project
2787
- * together. The route's check and this one are not redundant: the route's
2788
- * answers an authorisation question (403, and only for HTTP callers), this
2789
- * one is a data-scoping guard that also covers the on-call banner and any
2790
- * other in-process caller that never passes through the route at all.
2791
- */
2792
- const scoped: CapturedFindBy = firstCall(teamFindOneBy);
2793
- expect(scoped.query["_id"]).toBe(teamId.toString());
2794
- expect((scoped.query["projectId"] as ObjectID).toString()).toBe(
2795
- projectId.toString(),
2796
- );
2797
- expect(scoped.props?.isRoot).toBe(true);
2798
- });
2081
+ },
2082
+ );
2799
2083
  });