@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,52 +1,59 @@
1
- import GlobalConfigAPI from "../../../Server/API/GlobalConfigAPI";
1
+ import GlobalConfigAPI, {
2
+ LICENSE_RESPONSE_CONFIG_SELECT,
3
+ } from "../../../Server/API/GlobalConfigAPI";
2
4
  import MasterAdminAuthorization from "../../../Server/Middleware/MasterAdminAuthorization";
5
+ import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
3
6
  import GlobalConfigService from "../../../Server/Services/GlobalConfigService";
4
- import UserService from "../../../Server/Services/UserService";
5
7
  import Response from "../../../Server/Utils/Response";
8
+ import { AppVersion } from "../../../Server/EnvironmentConfig";
9
+ import EnterpriseEdition from "../../../Server/Enterprise/EnterpriseEdition";
10
+ import EnterpriseFeature from "../../../Server/Enterprise/EnterpriseFeature";
11
+ import {
12
+ EnterpriseLicenseSnapshot,
13
+ EnterpriseLicenseStatus,
14
+ SeatUsage,
15
+ } from "../../../Server/Enterprise/EnterpriseLicenseSnapshot";
6
16
  import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
7
- import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
8
- import HTTPResponse from "../../../Types/API/HTTPResponse";
9
- import BadDataException from "../../../Types/Exception/BadDataException";
17
+ import Exception from "../../../Types/Exception/Exception";
18
+ import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
10
19
  import { JSONObject } from "../../../Types/JSON";
11
20
  import ObjectID from "../../../Types/ObjectID";
12
- import PositiveNumber from "../../../Types/PositiveNumber";
13
- import API from "../../../Utils/API";
21
+ import UserType from "../../../Types/UserType";
14
22
  import {
15
23
  NextFunction,
16
24
  OneUptimeRequest,
17
25
  OneUptimeResponse,
18
26
  } from "../../../Server/Utils/Express";
19
27
  import { mockRouter } from "./Helpers";
20
- import { beforeEach, afterEach, describe, expect, it } from "@jest/globals";
28
+ import FakeEnterpriseModule, {
29
+ createLicenseSnapshot,
30
+ createLicenseSnapshotWithStatus,
31
+ installFakeEnterpriseModule,
32
+ uninstallEnterpriseModule,
33
+ } from "../Enterprise/FakeEnterpriseModule";
34
+ import { setTestBillingEnabled } from "../Enterprise/TestBillingFlag";
35
+ import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
21
36
 
22
37
  /*
23
- * The self-hosted half of the licence: activating a key, refreshing the key
24
- * the installation already holds, and reporting what the seat limit means
25
- * right now.
38
+ * GET /global-config/license: the one license route that stays in core.
26
39
  *
27
- * Two things brought this suite into being.
40
+ * Activating and refreshing a license moved into the Enterprise license client
41
+ * (ee/Server/License, tested in ee/Tests/Server/License). What stays here is
42
+ * the read, because the Community Edition still needs the version and
43
+ * update-check half of the edition dialog, and because the answer has to be
44
+ * cut down per caller:
28
45
  *
29
- * The seat limit is set on oneuptime.com and changes on any day — a customer
30
- * buys ten more seats at noon. It reaches the installation through the daily
31
- * report job, so until now the only way to apply it sooner was for somebody to
32
- * re-type the licence key into a box that is HIDDEN while the licence is
33
- * valid. /license/refresh is that missing button, and it exists precisely
34
- * because the installation now refuses users above the limit: waiting a day to
35
- * learn about seats you have already paid for is a different feature when the
36
- * old number is being enforced.
46
+ * - this route serves the signed-out login page, so it cannot require
47
+ * authentication, and anonymous callers used to be told the seat limit,
48
+ * the current user count and more;
49
+ * - any signed-in user - including a status-page subscriber, whose token
50
+ * also decodes to a userId - used to receive the license key, the token
51
+ * and the host of every instance on the license.
37
52
  *
38
- * And both writes used to run on UserMiddleware.getUserMiddleware, which lets
39
- * anonymous callers through — it has to, because the GET on the same path
40
- * serves the signed-out login page. That made the route that decides this
41
- * installation's seat ceiling reachable without signing in at all.
53
+ * Now a master admin gets everything and everybody else gets only what the
54
+ * edition pill shows.
42
55
  */
43
56
 
44
- /*
45
- * PasswordHash carries a pre-existing TS diagnostic that fails any suite whose
46
- * require graph reaches it, and BaseAPI's graph still reaches it. Replaced with
47
- * a factory rather than automocked, because an automock still type-checks the
48
- * real file.
49
- */
50
57
  jest.mock("../../../Server/Utils/PasswordHash", () => {
51
58
  return {
52
59
  __esModule: true,
@@ -60,7 +67,6 @@ jest.mock("../../../Server/Utils/PasswordHash", () => {
60
67
  };
61
68
  });
62
69
 
63
- // Same story as PasswordHash: a local-only diagnostic in a module dragged in.
64
70
  jest.mock("../../../Server/Utils/VerificationCode", () => {
65
71
  return {
66
72
  __esModule: true,
@@ -99,79 +105,19 @@ jest.mock("../../../Server/Utils/Response", () => {
99
105
  };
100
106
  });
101
107
 
102
- /*
103
- * The deployment flags live on globalThis rather than in module-scope
104
- * variables, and the getters below reference nothing but globalThis.
105
- *
106
- * jest hoists the mock factory above every declaration in this file, and
107
- * BaseAPI's import graph reaches IncidentFeedService, which reads
108
- * IsBillingEnabled at module scope. That read lands inside these getters
109
- * during the import — before a `let` in this file has initialised — and a
110
- * getter that closed over one would throw a temporal-dead-zone
111
- * ReferenceError before a single test ran. Absent flags read as false, which
112
- * is a fine thing for an unrelated service to see on its way past.
113
- *
114
- * They are also live accessors rather than values: the routes read the flags
115
- * when they run, and object spread would flatten them at import time.
116
- */
117
- const BILLING_FLAG_KEY: string = "__oneUptimeTestIsBillingEnabled";
118
- const ENTERPRISE_FLAG_KEY: string = "__oneUptimeTestIsEnterpriseEdition";
119
-
120
- type SetDeploymentFlagFunction = (key: string, value: boolean) => void;
121
-
122
- const setDeploymentFlag: SetDeploymentFlagFunction = (
123
- key: string,
124
- value: boolean,
125
- ): void => {
126
- (globalThis as unknown as Record<string, unknown>)[key] = value;
127
- };
128
-
108
+ // CI's config.env sets BILLING_ENABLED=true; this suite pins it per test.
129
109
  jest.mock("../../../Server/EnvironmentConfig", () => {
130
- const actual: Record<string, unknown> = jest.requireActual(
131
- "../../../Server/EnvironmentConfig",
132
- ) as Record<string, unknown>;
133
-
134
- const mocked: Record<string, unknown> = {
135
- ...actual,
136
- __esModule: true,
137
- };
138
-
139
- Object.defineProperty(mocked, "IsBillingEnabled", {
140
- get: (): boolean => {
141
- return (
142
- (globalThis as unknown as Record<string, unknown>)[
143
- "__oneUptimeTestIsBillingEnabled"
144
- ] === true
145
- );
146
- },
147
- });
148
-
149
- Object.defineProperty(mocked, "IsEnterpriseEdition", {
150
- get: (): boolean => {
151
- return (
152
- (globalThis as unknown as Record<string, unknown>)[
153
- "__oneUptimeTestIsEnterpriseEdition"
154
- ] === true
155
- );
156
- },
157
- });
158
-
159
- return mocked;
160
- });
161
-
162
- /*
163
- * Factories rather than automocks. An automock still loads the real module to
164
- * copy its shape, and loading UserService drags in most of the service graph -
165
- * some of which reads IsBillingEnabled at module scope, before the flags above
166
- * have initialised. Nothing here needs the real implementations.
167
- */
168
- jest.mock("../../../Utils/API", () => {
169
- return {
170
- __esModule: true,
171
- default: {
172
- post: jest.fn(),
173
- },
174
- };
110
+ const billingFlag: typeof import("../Enterprise/TestBillingFlag") =
111
+ jest.requireActual(
112
+ "../Enterprise/TestBillingFlag",
113
+ ) as typeof import("../Enterprise/TestBillingFlag");
114
+
115
+ return billingFlag.withLiveBillingFlag(
116
+ jest.requireActual("../../../Server/EnvironmentConfig") as Record<
117
+ string,
118
+ unknown
119
+ >,
120
+ );
175
121
  });
176
122
 
177
123
  jest.mock("../../../Server/Services/GlobalConfigService", () => {
@@ -198,7 +144,47 @@ const LICENSE_ROUTE: string = "/global-config/license";
198
144
  const REFRESH_ROUTE: string = "/global-config/license/refresh";
199
145
 
200
146
  const STORED_LICENSE_KEY: string = "acme-stored-license-key";
147
+ const STORED_TOKEN: string = "stored.license.token";
201
148
  const INSTANCE_ID: ObjectID = ObjectID.generate();
149
+ const DAY_IN_MS: number = 24 * 60 * 60 * 1000;
150
+
151
+ // Exactly what anybody but a master admin may learn.
152
+ const PUBLIC_KEYS: Array<string> = [
153
+ "companyName",
154
+ "edition",
155
+ "expiresAt",
156
+ "features",
157
+ "graceEndsAt",
158
+ "graceReason",
159
+ "isEvaluation",
160
+ "isEvaluationLicense",
161
+ "licenseValid",
162
+ "status",
163
+ "verification",
164
+ ].sort();
165
+
166
+ // Never in a response to anybody but a master admin.
167
+ const MASTER_ADMIN_ONLY_KEYS: Array<string> = [
168
+ "activationMode",
169
+ "canAddMoreUsers",
170
+ "currentUserCount",
171
+ "currentVersion",
172
+ "instanceId",
173
+ "instances",
174
+ "isSeatLimitEnforced",
175
+ "isUpdateAvailable",
176
+ "isUpdateCheckDisabled",
177
+ "latestVersion",
178
+ "latestVersionCheckedAt",
179
+ "latestVersionPublishedAt",
180
+ "licenseKey",
181
+ "message",
182
+ "seatsInUse",
183
+ "seatsRemaining",
184
+ "token",
185
+ "userCountUpdatedAt",
186
+ "userLimit",
187
+ ].sort();
202
188
 
203
189
  type MakeStoredConfigFunction = (
204
190
  overrides?: Record<string, unknown>,
@@ -211,103 +197,111 @@ const makeStoredConfig: MakeStoredConfigFunction = (
211
197
  config.id = ObjectID.getZeroObjectID();
212
198
  config.instanceId = INSTANCE_ID;
213
199
  config.enterpriseLicenseKey = STORED_LICENSE_KEY;
200
+ config.enterpriseLicenseToken = STORED_TOKEN;
201
+ config.enterpriseLicenseUserLimit = 999;
202
+ config.enterpriseLicenseCurrentUserCount = 42;
203
+ config.enterpriseLicenseUserCountUpdatedAt = new Date(
204
+ "2026-09-01T00:00:00.000Z",
205
+ );
206
+ config.enterpriseLicenseInstances = [
207
+ {
208
+ instanceId: INSTANCE_ID.toString(),
209
+ host: "prod.acme.internal",
210
+ userCount: 42,
211
+ lastReportedAt: "2026-09-01T00:00:00.000Z",
212
+ version: "13.0.0",
213
+ },
214
+ ];
215
+ config.latestReleaseVersion = "99.0.0";
216
+ config.latestReleasePublishedAt = new Date("2026-09-10T00:00:00.000Z");
217
+ config.latestReleaseCheckedAt = new Date("2026-09-11T00:00:00.000Z");
214
218
 
215
219
  return Object.assign(config, overrides || {});
216
220
  };
217
221
 
218
- type LicenseServerPayloadFunction = (
219
- overrides?: Record<string, unknown>,
220
- ) => JSONObject;
221
-
222
- const licenseServerPayload: LicenseServerPayloadFunction = (
223
- overrides?: Record<string, unknown>,
224
- ): JSONObject => {
225
- return {
226
- companyName: "Acme Inc",
227
- expiresAt: "2030-01-01T00:00:00.000Z",
228
- licenseKey: STORED_LICENSE_KEY,
229
- token: "signed.jwt.token",
230
- isEvaluationLicense: false,
231
- userLimit: 150,
232
- currentUserCount: 42,
233
- userCountUpdatedAt: "2026-01-01T00:00:00.000Z",
234
- instances: [],
235
- ...overrides,
236
- };
237
- };
238
-
239
- type GetResponseBodyFunction = () => JSONObject;
240
-
241
- const getResponseBody: GetResponseBodyFunction = (): JSONObject => {
242
- const calls: Array<Array<unknown>> = (
243
- Response.sendJsonObjectResponse as unknown as jest.Mock
244
- ).mock.calls as Array<Array<unknown>>;
245
-
246
- expect(calls).toHaveLength(1);
247
-
248
- return calls[0]![2] as JSONObject;
222
+ const ENFORCED_SEAT_USAGE: SeatUsage = {
223
+ isEnforced: true,
224
+ userLimit: 50,
225
+ seatsInUse: 49,
226
+ seatsRemaining: 1,
227
+ hasSeatForNewUser: true,
228
+ seatsUsedByOtherInstances: 0,
249
229
  };
250
230
 
251
- type GetStoredUpdateFunction = () => JSONObject;
252
-
253
- const getStoredUpdate: GetStoredUpdateFunction = (): JSONObject => {
254
- const calls: Array<Array<unknown>> = (
255
- GlobalConfigService.updateOneById as unknown as jest.Mock
256
- ).mock.calls as Array<Array<unknown>>;
257
-
258
- expect(calls.length).toBeGreaterThan(0);
259
-
260
- return (calls[0]![0] as Record<string, unknown>)["data"] as JSONObject;
261
- };
231
+ type CallerKind = "anonymous" | "signed-in user" | "master admin";
262
232
 
263
- describe("GlobalConfigAPI licence routes", () => {
233
+ describe("GET /global-config/license", () => {
264
234
  let mockRequest: OneUptimeRequest;
265
235
  let mockResponse: OneUptimeResponse;
266
236
  let nextFunction: NextFunction;
267
237
 
268
- type CallRouteFunction = (route: string) => Promise<void>;
238
+ const setCaller: (caller: CallerKind) => void = (
239
+ caller: CallerKind,
240
+ ): void => {
241
+ const request: Record<string, unknown> = mockRequest as unknown as Record<
242
+ string,
243
+ unknown
244
+ >;
245
+
246
+ if (caller === "anonymous") {
247
+ delete request["userAuthorization"];
248
+ return;
249
+ }
250
+
251
+ request["userAuthorization"] = {
252
+ userId: ObjectID.generate(),
253
+ isMasterAdmin: caller === "master admin",
254
+ };
255
+ };
269
256
 
270
- const callRoute: CallRouteFunction = async (route: string): Promise<void> => {
257
+ const callGet: () => Promise<JSONObject> = async (): Promise<JSONObject> => {
271
258
  await mockRouter
272
- .match("post", route)
259
+ .match("get", LICENSE_ROUTE)
273
260
  .handlerFunction(mockRequest, mockResponse, nextFunction);
274
- };
275
261
 
276
- type NextErrorFunction = () => Error;
262
+ expect(nextFunction).not.toHaveBeenCalled();
277
263
 
278
- const nextError: NextErrorFunction = (): Error => {
279
- const calls: Array<Array<unknown>> = (nextFunction as unknown as jest.Mock)
280
- .mock.calls as Array<Array<unknown>>;
264
+ const calls: Array<Array<unknown>> = (
265
+ Response.sendJsonObjectResponse as unknown as jest.Mock
266
+ ).mock.calls as Array<Array<unknown>>;
281
267
 
282
268
  expect(calls).toHaveLength(1);
283
269
 
284
- return calls[0]![0] as Error;
270
+ return calls[0]![2] as JSONObject;
271
+ };
272
+
273
+ const installWithSnapshot: (
274
+ snapshot: EnterpriseLicenseSnapshot | null,
275
+ seatUsage?: SeatUsage | null,
276
+ ) => FakeEnterpriseModule = (
277
+ snapshot: EnterpriseLicenseSnapshot | null,
278
+ seatUsage?: SeatUsage | null,
279
+ ): FakeEnterpriseModule => {
280
+ return installFakeEnterpriseModule({
281
+ snapshot,
282
+ seatUsage: seatUsage === undefined ? ENFORCED_SEAT_USAGE : seatUsage,
283
+ });
285
284
  };
286
285
 
287
286
  beforeEach(() => {
288
287
  jest.clearAllMocks();
289
288
  mockRouter.routes = [];
290
- setDeploymentFlag(BILLING_FLAG_KEY, false);
291
- setDeploymentFlag(ENTERPRISE_FLAG_KEY, true);
289
+ setTestBillingEnabled(false);
290
+ uninstallEnterpriseModule();
292
291
 
293
292
  new GlobalConfigAPI();
294
293
 
295
294
  GlobalConfigService.findOneById = jest
296
295
  .fn()
297
296
  .mockResolvedValue(makeStoredConfig());
298
- GlobalConfigService.updateOneById = jest.fn().mockResolvedValue(undefined);
299
- GlobalConfigService.create = jest.fn().mockResolvedValue(undefined);
300
-
301
- UserService.countBy = jest.fn().mockResolvedValue(new PositiveNumber(42));
302
-
303
- (API.post as unknown as jest.Mock) = jest
304
- .fn()
305
- .mockResolvedValue(
306
- new HTTPResponse<JSONObject>(200, licenseServerPayload(), {}),
307
- );
308
297
 
298
+ /*
299
+ * Express always gives a request a `query` object; the licence GET reads
300
+ * `signedIn` from it before anything else.
301
+ */
309
302
  mockRequest = {
310
303
  body: {},
304
+ query: {},
311
305
  } as unknown as OneUptimeRequest;
312
306
 
313
307
  mockResponse = {
@@ -320,338 +314,729 @@ describe("GlobalConfigAPI licence routes", () => {
320
314
  });
321
315
 
322
316
  afterEach(() => {
317
+ uninstallEnterpriseModule();
318
+ setTestBillingEnabled(false);
323
319
  jest.restoreAllMocks();
324
320
  });
325
321
 
326
- describe("who is allowed to write the licence", () => {
327
- /*
328
- * The seat limit these routes store is the number UserService refuses new
329
- * users against. UserMiddleware.getUserMiddleware — which the GET on the
330
- * same path uses, and has to, because it serves the signed-out login page —
331
- * lets anonymous callers straight through, so it is not a guard here.
332
- */
333
- it.each([
334
- ["activating a licence key", LICENSE_ROUTE],
335
- ["refreshing the stored licence", REFRESH_ROUTE],
336
- ])("requires a master admin for %s", (_label: string, route: string) => {
337
- expect(mockRouter.match("post", route).middlewares).toContain(
338
- MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
322
+ describe("the routes core still serves", () => {
323
+ it("serves the GET to anyone, so the login page keeps working", () => {
324
+ const route: ReturnType<typeof mockRouter.match> = mockRouter.match(
325
+ "get",
326
+ LICENSE_ROUTE,
339
327
  );
340
- });
341
328
 
342
- it("still serves the licence GET to anyone, so the login page keeps working", () => {
343
- expect(mockRouter.match("get", LICENSE_ROUTE).middlewares).not.toContain(
329
+ expect(route.middlewares).toEqual([UserMiddleware.getUserMiddleware]);
330
+ expect(route.middlewares).not.toContain(
344
331
  MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
345
332
  );
346
333
  });
334
+
335
+ /*
336
+ * Activation and refresh are the Enterprise license client's now. A copy
337
+ * left behind in core would be a second writer of the license, served
338
+ * even by the Community Edition.
339
+ */
340
+ it.each([LICENSE_ROUTE, REFRESH_ROUTE])(
341
+ "no longer registers POST %s in core",
342
+ (route: string) => {
343
+ const posts: Array<string> = mockRouter.routes
344
+ .filter((registered: { method: string; uri: string }): boolean => {
345
+ return registered.method === "POST";
346
+ })
347
+ .map((registered: { uri: string }): string => {
348
+ return registered.uri;
349
+ });
350
+
351
+ expect(posts).not.toContain(route);
352
+ },
353
+ );
354
+
355
+ it("keeps /global-config/vars", () => {
356
+ expect(
357
+ mockRouter.match("get", "/global-config/vars").handlerFunction,
358
+ ).toBeDefined();
359
+ });
347
360
  });
348
361
 
349
- describe("POST /global-config/license - activating a key", () => {
350
- it("rejects a request with no licence key", async () => {
351
- mockRequest.body = {};
362
+ describe("on the Community Edition", () => {
363
+ it.each([
364
+ "anonymous",
365
+ "signed-in user",
366
+ "master admin",
367
+ ] as Array<CallerKind>)(
368
+ "says community with no license to a %s",
369
+ async (caller: CallerKind) => {
370
+ setCaller(caller);
371
+
372
+ const body: JSONObject = await callGet();
373
+
374
+ expect(body["edition"]).toBe("community");
375
+ expect(body["status"]).toBeNull();
376
+ expect(body["verification"]).toBeNull();
377
+ expect(body["licenseValid"]).toBe(false);
378
+ expect(body["companyName"]).toBeNull();
379
+ expect(body["expiresAt"]).toBeNull();
380
+ },
381
+ );
352
382
 
353
- await callRoute(LICENSE_ROUTE);
383
+ /*
384
+ * The Community Edition dialog still has a version card: which build is
385
+ * running and whether a newer one exists is a property of the
386
+ * installation, not of a license.
387
+ */
388
+ it("still gives a master admin the version and update-check fields", async () => {
389
+ setCaller("master admin");
390
+
391
+ const body: JSONObject = await callGet();
354
392
 
355
- expect(nextError()).toBeInstanceOf(BadDataException);
356
- expect(API.post).not.toHaveBeenCalled();
393
+ expect(body["currentVersion"]).toBe(AppVersion);
394
+ expect(body["latestVersion"]).toBe("99.0.0");
395
+ expect(body["latestVersionPublishedAt"]).toBe("2026-09-10T00:00:00.000Z");
396
+ expect(body["latestVersionCheckedAt"]).toBe("2026-09-11T00:00:00.000Z");
397
+ expect(typeof body["isUpdateAvailable"]).toBe("boolean");
398
+ expect(typeof body["isUpdateCheckDisabled"]).toBe("boolean");
357
399
  });
358
400
 
359
- it("rejects a licence key that is only whitespace", async () => {
360
- mockRequest.body = { licenseKey: " " };
401
+ /*
402
+ * A key or token left over from an earlier Enterprise image licenses
403
+ * nothing on the Community Edition, so it is not reported as if it did.
404
+ */
405
+ it("reports no license key, token, topology or seats even when old license columns remain", async () => {
406
+ setCaller("master admin");
407
+
408
+ const body: JSONObject = await callGet();
409
+
410
+ expect(body["licenseKey"]).toBeNull();
411
+ expect(body["token"]).toBeNull();
412
+ expect(body["activationMode"]).toBeNull();
413
+ expect(body["instances"]).toEqual([]);
414
+ expect(body["instanceId"]).toBeNull();
415
+ expect(body["userLimit"]).toBeNull();
416
+ expect(body["currentUserCount"]).toBeNull();
417
+ expect(body["isSeatLimitEnforced"]).toBe(false);
418
+ expect(body["canAddMoreUsers"]).toBe(true);
419
+ });
420
+
421
+ it("gives everybody else only the public fields and reads nothing", async () => {
422
+ setCaller("signed-in user");
361
423
 
362
- await callRoute(LICENSE_ROUTE);
424
+ const body: JSONObject = await callGet();
363
425
 
364
- expect(nextError()).toBeInstanceOf(BadDataException);
426
+ expect(Object.keys(body).sort()).toEqual(PUBLIC_KEYS);
427
+ expect(GlobalConfigService.findOneById).not.toHaveBeenCalled();
365
428
  });
429
+ });
366
430
 
367
- it("validates the supplied key against oneuptime.com", async () => {
368
- mockRequest.body = { licenseKey: " a-new-key " };
431
+ describe("on the Enterprise Edition - what each caller gets", () => {
432
+ beforeEach(() => {
433
+ installWithSnapshot(createLicenseSnapshot({ userLimit: 50 }));
434
+ });
435
+
436
+ it.each(["anonymous", "signed-in user"] as Array<CallerKind>)(
437
+ "gives a %s exactly the public fields",
438
+ async (caller: CallerKind) => {
439
+ setCaller(caller);
440
+
441
+ const body: JSONObject = await callGet();
369
442
 
370
- await callRoute(LICENSE_ROUTE);
443
+ expect(Object.keys(body).sort()).toEqual(PUBLIC_KEYS);
371
444
 
372
- const sent: JSONObject = (
373
- (API.post as unknown as jest.Mock).mock.calls[0]![0] as Record<
374
- string,
375
- JSONObject
376
- >
377
- )["data"] as JSONObject;
445
+ for (const key of MASTER_ADMIN_ONLY_KEYS) {
446
+ expect(body).not.toHaveProperty(key);
447
+ }
448
+ },
449
+ );
450
+
451
+ /*
452
+ * The finding this closes: a signed-in (non-admin) user, or anybody whose
453
+ * token decodes to a userId, used to receive the license key, the token
454
+ * and every instance's host.
455
+ */
456
+ it("never gives a signed-in user who is not a master admin the key, the token or the topology", async () => {
457
+ setCaller("signed-in user");
378
458
 
379
- expect(sent["licenseKey"]).toBe("a-new-key");
380
- expect(sent["instanceId"]).toBe(INSTANCE_ID.toString());
459
+ const body: JSONObject = await callGet();
460
+ const serialized: string = JSON.stringify(body);
461
+
462
+ expect(serialized).not.toContain(STORED_LICENSE_KEY);
463
+ expect(serialized).not.toContain(STORED_TOKEN);
464
+ expect(serialized).not.toContain("prod.acme.internal");
465
+ expect(serialized).not.toContain(INSTANCE_ID.toString());
381
466
  });
382
467
 
383
- it("stores the seat limit the licence server reported", async () => {
384
- mockRequest.body = { licenseKey: STORED_LICENSE_KEY };
468
+ it("does not read the config row or count seats for anybody but a master admin", async () => {
469
+ const fake: FakeEnterpriseModule = installWithSnapshot(
470
+ createLicenseSnapshot(),
471
+ );
472
+ const seatUsageSpy: jest.SpyInstance = jest.spyOn(
473
+ fake.licensing,
474
+ "getSeatUsage",
475
+ );
476
+
477
+ setCaller("anonymous");
385
478
 
386
- await callRoute(LICENSE_ROUTE);
479
+ await callGet();
387
480
 
388
- expect(getStoredUpdate()["enterpriseLicenseUserLimit"]).toBe(150);
481
+ expect(GlobalConfigService.findOneById).not.toHaveBeenCalled();
482
+ expect(seatUsageSpy).not.toHaveBeenCalled();
483
+ });
484
+
485
+ it("gives a master admin the public fields plus everything needed to manage the license", async () => {
486
+ setCaller("master admin");
487
+
488
+ const body: JSONObject = await callGet();
489
+
490
+ expect(Object.keys(body).sort()).toEqual(
491
+ [...PUBLIC_KEYS, ...MASTER_ADMIN_ONLY_KEYS].sort(),
492
+ );
493
+ expect(body["licenseKey"]).toBe(STORED_LICENSE_KEY);
494
+ expect(body["token"]).toBe(STORED_TOKEN);
495
+ expect(body["instanceId"]).toBe(INSTANCE_ID.toString());
496
+ expect(body["instances"]).toHaveLength(1);
497
+ expect(body["currentUserCount"]).toBe(42);
498
+ expect(body["userCountUpdatedAt"]).toBe("2026-09-01T00:00:00.000Z");
499
+ expect(body["currentVersion"]).toBe(AppVersion);
500
+ expect(body["activationMode"]).toBe("online");
501
+ });
502
+
503
+ it("reads the row with exactly the columns the response needs, as root", async () => {
504
+ setCaller("master admin");
505
+
506
+ await callGet();
507
+
508
+ const call: Record<string, unknown> = (
509
+ GlobalConfigService.findOneById as unknown as jest.Mock
510
+ ).mock.calls[0]![0] as Record<string, unknown>;
511
+
512
+ expect(call["select"]).toEqual(LICENSE_RESPONSE_CONFIG_SELECT);
513
+ expect((call["props"] as Record<string, unknown>)["isRoot"]).toBe(true);
389
514
  });
390
- });
391
515
 
392
- describe("POST /global-config/license/refresh", () => {
393
516
  /*
394
- * The whole point of the route: no key in the body. A refresh that
395
- * accepted one would be an activation with a friendlier name, and a
396
- * mistyped key would be able to replace a working licence by accident.
517
+ * A verified license's limits come from its signed claims; the column is
518
+ * only a mirror a master admin could once overwrite. The snapshot is the
519
+ * truth, so the response reports its limit, not the column's 999.
397
520
  */
398
- it("refreshes using the stored key and ignores anything in the body", async () => {
399
- mockRequest.body = { licenseKey: "somebody-elses-key" };
521
+ it("reports the seat limit from the license snapshot, not the stored column", async () => {
522
+ setCaller("master admin");
523
+
524
+ const body: JSONObject = await callGet();
525
+
526
+ expect(body["userLimit"]).toBe(50);
527
+ });
528
+
529
+ it("reports the seat enforcement the license client computed", async () => {
530
+ setCaller("master admin");
531
+
532
+ const body: JSONObject = await callGet();
533
+
534
+ expect(body["isSeatLimitEnforced"]).toBe(true);
535
+ expect(body["seatsInUse"]).toBe(49);
536
+ expect(body["seatsRemaining"]).toBe(1);
537
+ expect(body["canAddMoreUsers"]).toBe(true);
538
+ });
400
539
 
401
- await callRoute(REFRESH_ROUTE);
540
+ it("says the limit is not enforced when the license client reports no usage", async () => {
541
+ installWithSnapshot(createLicenseSnapshot(), null);
542
+ setCaller("master admin");
402
543
 
403
- const sent: JSONObject = (
404
- (API.post as unknown as jest.Mock).mock.calls[0]![0] as Record<
405
- string,
406
- JSONObject
407
- >
408
- )["data"] as JSONObject;
544
+ const body: JSONObject = await callGet();
409
545
 
410
- expect(sent["licenseKey"]).toBe(STORED_LICENSE_KEY);
546
+ expect(body["isSeatLimitEnforced"]).toBe(false);
547
+ expect(body["seatsInUse"]).toBeNull();
548
+ expect(body["seatsRemaining"]).toBeNull();
549
+ expect(body["canAddMoreUsers"]).toBe(true);
411
550
  });
412
551
 
413
- it("refuses to refresh an installation that has no licence key yet", async () => {
552
+ it("calls an installation with a token but no key offline-activated", async () => {
414
553
  GlobalConfigService.findOneById = jest
415
554
  .fn()
416
555
  .mockResolvedValue(
417
556
  makeStoredConfig({ enterpriseLicenseKey: undefined }),
418
557
  );
558
+ setCaller("master admin");
419
559
 
420
- await callRoute(REFRESH_ROUTE);
560
+ const body: JSONObject = await callGet();
421
561
 
422
- expect(nextError()).toBeInstanceOf(BadDataException);
423
- expect(API.post).not.toHaveBeenCalled();
562
+ expect(body["activationMode"]).toBe("offline");
563
+ expect(body["licenseKey"]).toBeNull();
424
564
  });
425
565
 
426
- it("refuses to refresh when there is no config row at all", async () => {
566
+ it("has no activation mode when there is no token at all", async () => {
567
+ GlobalConfigService.findOneById = jest.fn().mockResolvedValue(
568
+ makeStoredConfig({
569
+ enterpriseLicenseToken: undefined,
570
+ }),
571
+ );
572
+ setCaller("master admin");
573
+
574
+ const body: JSONObject = await callGet();
575
+
576
+ expect(body["activationMode"]).toBeNull();
577
+ });
578
+
579
+ it("survives a missing config row", async () => {
427
580
  GlobalConfigService.findOneById = jest.fn().mockResolvedValue(null);
581
+ setCaller("master admin");
428
582
 
429
- await callRoute(REFRESH_ROUTE);
583
+ const body: JSONObject = await callGet();
430
584
 
431
- expect(nextError()).toBeInstanceOf(BadDataException);
432
- expect(API.post).not.toHaveBeenCalled();
585
+ expect(body["licenseKey"]).toBeNull();
586
+ expect(body["instances"]).toEqual([]);
587
+ expect(body["latestVersion"]).toBeNull();
588
+ expect(body["edition"]).toBe("enterprise");
433
589
  });
590
+ });
434
591
 
592
+ describe("on the Enterprise Edition - what each license status looks like", () => {
435
593
  /*
436
- * The reason the button exists. The customer raised the limit on
437
- * oneuptime.com; the stored 50 is what this installation is refusing users
438
- * against until something writes the new number down.
594
+ * licenseValid is the pill's verdict. Grace must read as valid: nothing
595
+ * has changed yet, and telling an administrator the license is gone while
596
+ * everything still works would be a false alarm.
439
597
  */
440
- it("applies a seat limit that has been raised on oneuptime.com", async () => {
441
- (API.post as unknown as jest.Mock).mockResolvedValue(
442
- new HTTPResponse<JSONObject>(
443
- 200,
444
- licenseServerPayload({ userLimit: 500 }),
445
- {},
446
- ),
598
+ it.each<[EnterpriseLicenseStatus, boolean]>([
599
+ ["valid", true],
600
+ ["grace", true],
601
+ ["expired", false],
602
+ ["missing", false],
603
+ ["invalid", false],
604
+ ])(
605
+ "status %s reads as licenseValid=%s to everybody",
606
+ async (status: EnterpriseLicenseStatus, expected: boolean) => {
607
+ installWithSnapshot(createLicenseSnapshotWithStatus(status));
608
+
609
+ for (const caller of [
610
+ "anonymous",
611
+ "signed-in user",
612
+ "master admin",
613
+ ] as Array<CallerKind>) {
614
+ (Response.sendJsonObjectResponse as unknown as jest.Mock).mockClear();
615
+ setCaller(caller);
616
+
617
+ const body: JSONObject = await callGet();
618
+
619
+ expect(body["edition"]).toBe("enterprise");
620
+ expect(body["status"]).toBe(status);
621
+ expect(body["licenseValid"]).toBe(expected);
622
+ }
623
+ },
624
+ );
625
+
626
+ it("tells everybody when an expired license's grace period ends", async () => {
627
+ const graceEndsAt: Date = new Date(Date.now() + 5 * DAY_IN_MS);
628
+ const expiresAt: Date = new Date(Date.now() - 9 * DAY_IN_MS);
629
+
630
+ installWithSnapshot(
631
+ createLicenseSnapshotWithStatus("grace", {
632
+ graceReason: "expired",
633
+ graceEndsAt,
634
+ expiresAt,
635
+ }),
447
636
  );
637
+ setCaller("anonymous");
638
+
639
+ const body: JSONObject = await callGet();
448
640
 
449
- await callRoute(REFRESH_ROUTE);
641
+ expect(body["graceReason"]).toBe("expired");
642
+ expect(body["graceEndsAt"]).toBe(graceEndsAt.toISOString());
643
+ expect(body["expiresAt"]).toBe(expiresAt.toISOString());
644
+ });
450
645
 
451
- expect(getStoredUpdate()["enterpriseLicenseUserLimit"]).toBe(500);
452
- expect(getResponseBody()["userLimit"]).toBe(500);
646
+ it("reports an unlicensed installation inside its first-seen grace as a trial", async () => {
647
+ const graceEndsAt: Date = new Date(Date.now() + 10 * DAY_IN_MS);
648
+
649
+ installWithSnapshot({
650
+ status: "grace",
651
+ verification: "none",
652
+ graceReason: "unlicensed",
653
+ graceEndsAt,
654
+ userLimit: null,
655
+ isEvaluation: false,
656
+ features: "all",
657
+ });
658
+ setCaller("signed-in user");
659
+
660
+ const body: JSONObject = await callGet();
661
+
662
+ expect(body["status"]).toBe("grace");
663
+ expect(body["graceReason"]).toBe("unlicensed");
664
+ expect(body["verification"]).toBe("none");
665
+ expect(body["licenseValid"]).toBe(true);
666
+ expect(body["graceEndsAt"]).toBe(graceEndsAt.toISOString());
667
+ expect(body["companyName"]).toBeNull();
453
668
  });
454
669
 
455
- it("applies a seat limit that has been lowered on oneuptime.com", async () => {
456
- (API.post as unknown as jest.Mock).mockResolvedValue(
457
- new HTTPResponse<JSONObject>(
458
- 200,
459
- licenseServerPayload({ userLimit: 5 }),
460
- {},
461
- ),
670
+ it("reports an unverified legacy license as valid but unverified", async () => {
671
+ installWithSnapshot(
672
+ createLicenseSnapshot({ verification: "unverified" }),
462
673
  );
674
+ setCaller("anonymous");
463
675
 
464
- await callRoute(REFRESH_ROUTE);
676
+ const body: JSONObject = await callGet();
465
677
 
466
- expect(getStoredUpdate()["enterpriseLicenseUserLimit"]).toBe(5);
678
+ expect(body["licenseValid"]).toBe(true);
679
+ expect(body["verification"]).toBe("unverified");
467
680
  });
468
681
 
469
- it("clears the seat limit when the licence no longer carries one", async () => {
470
- (API.post as unknown as jest.Mock).mockResolvedValue(
471
- new HTTPResponse<JSONObject>(
472
- 200,
473
- licenseServerPayload({ userLimit: null }),
474
- {},
475
- ),
476
- );
682
+ it("reports the evaluation flag under both its names", async () => {
683
+ installWithSnapshot(createLicenseSnapshot({ isEvaluation: true }));
684
+ setCaller("anonymous");
477
685
 
478
- await callRoute(REFRESH_ROUTE);
686
+ const body: JSONObject = await callGet();
479
687
 
480
- expect(getStoredUpdate()["enterpriseLicenseUserLimit"]).toBeNull();
688
+ expect(body["isEvaluation"]).toBe(true);
689
+ expect(body["isEvaluationLicense"]).toBe(true);
481
690
  });
482
691
 
483
- it("refreshes the expiry as well as the seat limit", async () => {
484
- (API.post as unknown as jest.Mock).mockResolvedValue(
485
- new HTTPResponse<JSONObject>(
486
- 200,
487
- licenseServerPayload({ expiresAt: "2031-06-01T00:00:00.000Z" }),
488
- {},
489
- ),
692
+ it("explains an invalid license to a master admin only", async () => {
693
+ installWithSnapshot(
694
+ createLicenseSnapshotWithStatus("invalid", {
695
+ message: "The license is bound to a different OneUptime instance.",
696
+ }),
490
697
  );
491
698
 
492
- await callRoute(REFRESH_ROUTE);
699
+ setCaller("master admin");
700
+ expect((await callGet())["message"]).toBe(
701
+ "The license is bound to a different OneUptime instance.",
702
+ );
493
703
 
494
- expect(
495
- (
496
- getStoredUpdate()["enterpriseLicenseExpiresAt"] as unknown as Date
497
- ).toISOString(),
498
- ).toBe("2031-06-01T00:00:00.000Z");
704
+ (Response.sendJsonObjectResponse as unknown as jest.Mock).mockClear();
705
+ setCaller("signed-in user");
706
+ expect(await callGet()).not.toHaveProperty("message");
499
707
  });
500
708
 
501
709
  /*
502
- * An installation that cannot reach oneuptime.com must be told so rather
503
- * than quietly keeping the old terms and reporting success — the
504
- * administrator pressed this button precisely because they believe the old
505
- * terms are wrong.
710
+ * The snapshot could not be read at all (the facade falls back to the
711
+ * cached snapshot, and there is none yet). The answer is "not licensed",
712
+ * never an error page on the login screen.
506
713
  */
507
- it("surfaces a failure from the licence server instead of storing anything", async () => {
508
- (API.post as unknown as jest.Mock).mockResolvedValue(
509
- new HTTPErrorResponse(500, { message: "License key is invalid" }, {}),
510
- );
714
+ it("reads as not licensed when no snapshot is available", async () => {
715
+ const fake: FakeEnterpriseModule = installWithSnapshot(null);
716
+ fake.licensing.getSnapshotError = new Error("database is down");
717
+ setCaller("anonymous");
511
718
 
512
- await callRoute(REFRESH_ROUTE);
719
+ const body: JSONObject = await callGet();
513
720
 
514
- const error: Error = nextError();
515
-
516
- expect(error).toBeInstanceOf(BadDataException);
517
- expect(error.message).toBe("License key is invalid");
518
- expect(GlobalConfigService.updateOneById).not.toHaveBeenCalled();
721
+ expect(body["edition"]).toBe("enterprise");
722
+ expect(body["status"]).toBeNull();
723
+ expect(body["licenseValid"]).toBe(false);
519
724
  });
520
725
 
521
- it("does not store anything when the returned expiry is not a date", async () => {
522
- (API.post as unknown as jest.Mock).mockResolvedValue(
523
- new HTTPResponse<JSONObject>(
524
- 200,
525
- licenseServerPayload({ expiresAt: "the-first-of-never" }),
526
- {},
527
- ),
726
+ it("asks the facade for the snapshot rather than reading license columns itself", async () => {
727
+ installWithSnapshot(createLicenseSnapshot());
728
+ const snapshotSpy: jest.SpyInstance = jest.spyOn(
729
+ EnterpriseEdition,
730
+ "getLicenseSnapshot",
528
731
  );
732
+ setCaller("anonymous");
529
733
 
530
- await callRoute(REFRESH_ROUTE);
734
+ await callGet();
531
735
 
532
- expect(nextError()).toBeInstanceOf(BadDataException);
533
- expect(GlobalConfigService.updateOneById).not.toHaveBeenCalled();
736
+ expect(snapshotSpy).toHaveBeenCalledTimes(1);
534
737
  });
535
738
  });
536
739
 
537
- describe("the seat enforcement the response reports", () => {
538
- it("reports the seats in use against the freshly refreshed limit", async () => {
539
- UserService.countBy = jest.fn().mockResolvedValue(new PositiveNumber(42));
740
+ /*
741
+ * THE EXPIRED-SESSION CASE
742
+ *
743
+ * The dashboard's access-token cookie expires with the JWT inside it, so a
744
+ * signed-in tab left idle past the token lifetime asks for the licence
745
+ * with no session at all. On its own this route cannot tell that apart
746
+ * from the login page, and answers with the reduced anonymous payload and
747
+ * a 200 - which the edition pill reads as "no licence, no instances". The
748
+ * dashboards now say `?signedIn=true`; a caller that says so but carries
749
+ * no credentials is answered 401, the one status the browser client
750
+ * refreshes the session and replays on.
751
+ */
752
+ describe("?signedIn=true", () => {
753
+ const setRequest: (fields: Record<string, unknown>) => void = (
754
+ fields: Record<string, unknown>,
755
+ ): void => {
756
+ Object.assign(mockRequest as unknown as Record<string, unknown>, fields);
757
+ };
540
758
 
541
- await callRoute(REFRESH_ROUTE);
759
+ const callGetExpectingError: () => Promise<Error> =
760
+ async (): Promise<Error> => {
761
+ await mockRouter
762
+ .match("get", LICENSE_ROUTE)
763
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
542
764
 
543
- const body: JSONObject = getResponseBody();
765
+ expect(nextFunction).toHaveBeenCalledTimes(1);
544
766
 
545
- expect(body["isSeatLimitEnforced"]).toBe(true);
546
- expect(body["seatsInUse"]).toBe(42);
547
- expect(body["seatsRemaining"]).toBe(108);
548
- expect(body["canAddMoreUsers"]).toBe(true);
549
- });
767
+ return (nextFunction as unknown as jest.Mock).mock
768
+ .calls[0]![0] as Error;
769
+ };
550
770
 
551
- /*
552
- * The live count is what enforcement uses, and it is allowed to be higher
553
- * than the licence-wide figure oneuptime.com last computed — that figure is
554
- * up to a day old.
555
- */
556
- it("prefers the live user count over the licence server's stale one", async () => {
557
- UserService.countBy = jest
558
- .fn()
559
- .mockResolvedValue(new PositiveNumber(150));
771
+ it.each([
772
+ ["no userType at all", {}],
773
+ [
774
+ "userType Public (what getUserMiddleware stamps)",
775
+ { userType: UserType.Public },
776
+ ],
777
+ ])(
778
+ "answers an anonymous caller (%s) with a 401 before reading anything",
779
+ async (_label: string, caller: Record<string, unknown>) => {
780
+ installWithSnapshot(createLicenseSnapshot());
781
+ const snapshotSpy: jest.SpyInstance = jest.spyOn(
782
+ EnterpriseEdition,
783
+ "getLicenseSnapshot",
784
+ );
560
785
 
561
- await callRoute(REFRESH_ROUTE);
786
+ setRequest({ ...caller, query: { signedIn: "true" } });
562
787
 
563
- const body: JSONObject = getResponseBody();
788
+ const error: Error = await callGetExpectingError();
564
789
 
565
- expect(body["seatsInUse"]).toBe(150);
566
- expect(body["seatsRemaining"]).toBe(0);
567
- expect(body["canAddMoreUsers"]).toBe(false);
790
+ expect(error).toBeInstanceOf(NotAuthenticatedException);
791
+ expect((error as Exception).code).toBe(401);
792
+ expect(error.message).toBe(
793
+ UserMiddleware.AUTHENTICATION_REQUIRED_MESSAGE,
794
+ );
795
+ expect(snapshotSpy).not.toHaveBeenCalled();
796
+ expect(GlobalConfigService.findOneById).not.toHaveBeenCalled();
797
+ expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
798
+ },
799
+ );
800
+
801
+ it("answers the 401 on the Community Edition too", async () => {
802
+ setRequest({ userType: UserType.Public, query: { signedIn: "true" } });
803
+
804
+ const error: Error = await callGetExpectingError();
805
+
806
+ expect(error).toBeInstanceOf(NotAuthenticatedException);
807
+ expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
568
808
  });
569
809
 
570
- it("says the limit is not enforced when the licence has none", async () => {
571
- (API.post as unknown as jest.Mock).mockResolvedValue(
572
- new HTTPResponse<JSONObject>(
573
- 200,
574
- licenseServerPayload({ userLimit: null }),
575
- {},
576
- ),
577
- );
810
+ // The login page does not send the flag and must keep working.
811
+ it("still serves an anonymous caller without the flag the public payload", async () => {
812
+ installWithSnapshot(createLicenseSnapshot());
813
+ setRequest({ userType: UserType.Public, query: {} });
578
814
 
579
- await callRoute(REFRESH_ROUTE);
815
+ const body: JSONObject = await callGet();
580
816
 
581
- const body: JSONObject = getResponseBody();
817
+ expect(Object.keys(body).sort()).toEqual(PUBLIC_KEYS);
818
+ expect(body["licenseKey"]).toBeUndefined();
819
+ });
582
820
 
583
- expect(body["isSeatLimitEnforced"]).toBe(false);
584
- expect(body["seatsInUse"]).toBeNull();
585
- expect(body["canAddMoreUsers"]).toBe(true);
821
+ it.each([
822
+ ["signedIn=false", { signedIn: "false" }],
823
+ ["an empty signedIn", { signedIn: "" }],
824
+ ["signedIn=1", { signedIn: "1" }],
825
+ ])(
826
+ 'only the exact string "true" asks for a 401 (%s serves the public payload)',
827
+ async (_label: string, query: Record<string, unknown>) => {
828
+ setRequest({ userType: UserType.Public, query: query });
829
+
830
+ const body: JSONObject = await callGet();
831
+
832
+ expect(Object.keys(body).sort()).toEqual(PUBLIC_KEYS);
833
+ },
834
+ );
835
+
836
+ it("serves a signed-in master admin the full payload with the flag", async () => {
837
+ installWithSnapshot(createLicenseSnapshot({ userLimit: 50 }));
838
+ setCaller("master admin");
839
+ setRequest({ userType: UserType.User, query: { signedIn: "true" } });
840
+
841
+ const body: JSONObject = await callGet();
842
+
843
+ expect(body["licenseKey"]).toBe(STORED_LICENSE_KEY);
844
+ expect(body["instanceId"]).toBe(INSTANCE_ID.toString());
586
845
  });
587
846
 
588
- it("says the limit is not enforced on Community Edition", async () => {
589
- setDeploymentFlag(ENTERPRISE_FLAG_KEY, false);
847
+ it("serves a signed-in user who is not a master admin the public payload with the flag", async () => {
848
+ installWithSnapshot(createLicenseSnapshot());
849
+ setCaller("signed-in user");
850
+ setRequest({ userType: UserType.User, query: { signedIn: "true" } });
590
851
 
591
- await callRoute(REFRESH_ROUTE);
852
+ const body: JSONObject = await callGet();
592
853
 
593
- const body: JSONObject = getResponseBody();
854
+ expect(Object.keys(body).sort()).toEqual(PUBLIC_KEYS);
855
+ });
594
856
 
595
- expect(body["isSeatLimitEnforced"]).toBe(false);
596
- expect(body["canAddMoreUsers"]).toBe(true);
597
- expect(UserService.countBy).not.toHaveBeenCalled();
857
+ /*
858
+ * A project API key has credentials, so it is not an expired session:
859
+ * no 401. It has no user either, so it still gets only what an
860
+ * anonymous caller gets.
861
+ */
862
+ it("does not 401 an API-key caller that sends the flag", async () => {
863
+ installWithSnapshot(createLicenseSnapshot());
864
+ setRequest({ userType: UserType.API, query: { signedIn: "true" } });
865
+
866
+ const body: JSONObject = await callGet();
867
+
868
+ expect(Object.keys(body).sort()).toEqual(PUBLIC_KEYS);
598
869
  });
599
870
  });
600
871
 
601
- describe("GET /global-config/license", () => {
602
- type CallGetFunction = () => Promise<void>;
872
+ describe("errors", () => {
873
+ it("passes a database failure to next instead of answering", async () => {
874
+ GlobalConfigService.findOneById = jest
875
+ .fn()
876
+ .mockRejectedValue(new Error("database is down"));
877
+ setCaller("master admin");
603
878
 
604
- const callGet: CallGetFunction = async (): Promise<void> => {
605
879
  await mockRouter
606
880
  .match("get", LICENSE_ROUTE)
607
881
  .handlerFunction(mockRequest, mockResponse, nextFunction);
882
+
883
+ expect(nextFunction).toHaveBeenCalledTimes(1);
884
+ expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
885
+ });
886
+ });
887
+ });
888
+
889
+ describe("GlobalConfigAPI.buildLicenseResponse", () => {
890
+ it("ignores a snapshot handed to it when the Enterprise Edition is not loaded", () => {
891
+ const body: JSONObject = GlobalConfigAPI.buildLicenseResponse({
892
+ audience: "master-admin",
893
+ isEnterpriseEditionLoaded: false,
894
+ snapshot: createLicenseSnapshot(),
895
+ config: makeStoredConfig(),
896
+ seatUsage: ENFORCED_SEAT_USAGE,
897
+ });
898
+
899
+ expect(body["edition"]).toBe("community");
900
+ expect(body["licenseValid"]).toBe(false);
901
+ expect(body["licenseKey"]).toBeNull();
902
+ expect(body["isSeatLimitEnforced"]).toBe(false);
903
+ });
904
+
905
+ it("has the same keys for every audience whatever the license state", () => {
906
+ const statuses: Array<EnterpriseLicenseStatus> = [
907
+ "valid",
908
+ "grace",
909
+ "expired",
910
+ "missing",
911
+ "invalid",
912
+ ];
913
+
914
+ for (const status of statuses) {
915
+ const publicBody: JSONObject = GlobalConfigAPI.buildLicenseResponse({
916
+ audience: "public",
917
+ isEnterpriseEditionLoaded: true,
918
+ snapshot: createLicenseSnapshotWithStatus(status),
919
+ config: makeStoredConfig(),
920
+ seatUsage: ENFORCED_SEAT_USAGE,
921
+ });
922
+ const adminBody: JSONObject = GlobalConfigAPI.buildLicenseResponse({
923
+ audience: "master-admin",
924
+ isEnterpriseEditionLoaded: true,
925
+ snapshot: createLicenseSnapshotWithStatus(status),
926
+ config: makeStoredConfig(),
927
+ seatUsage: ENFORCED_SEAT_USAGE,
928
+ });
929
+
930
+ expect(Object.keys(publicBody).sort()).toEqual(PUBLIC_KEYS);
931
+ expect(Object.keys(adminBody).sort()).toEqual(
932
+ [...PUBLIC_KEYS, ...MASTER_ADMIN_ONLY_KEYS].sort(),
933
+ );
934
+ }
935
+ });
936
+
937
+ describe("features", () => {
938
+ const buildPublic: (
939
+ snapshot: EnterpriseLicenseSnapshot | null,
940
+ isEnterpriseEditionLoaded?: boolean,
941
+ ) => JSONObject = (
942
+ snapshot: EnterpriseLicenseSnapshot | null,
943
+ isEnterpriseEditionLoaded?: boolean,
944
+ ): JSONObject => {
945
+ return GlobalConfigAPI.buildLicenseResponse({
946
+ audience: "public",
947
+ isEnterpriseEditionLoaded: isEnterpriseEditionLoaded !== false,
948
+ snapshot,
949
+ config: null,
950
+ seatUsage: null,
951
+ });
608
952
  };
609
953
 
610
- beforeEach(() => {
611
- GlobalConfigService.findOneById = jest.fn().mockResolvedValue(
612
- makeStoredConfig({
613
- enterpriseLicenseUserLimit: 150,
614
- enterpriseLicenseCurrentUserCount: 42,
615
- enterpriseLicenseInstances: [],
954
+ it('is "all" for a license that covers everything', () => {
955
+ expect(buildPublic(createLicenseSnapshot())["features"]).toBe("all");
956
+ });
957
+
958
+ /*
959
+ * A valid license that leaves SSO out stops SSO (isFeatureActive), and
960
+ * licenseValid alone cannot tell a settings page that.
961
+ */
962
+ it("lists the features of a license that names them, so a page can tell one is left out", () => {
963
+ const body: JSONObject = buildPublic(
964
+ createLicenseSnapshot({
965
+ features: [EnterpriseFeature.SCIM, EnterpriseFeature.AuditLogs],
616
966
  }),
617
967
  );
968
+
969
+ expect(body["licenseValid"]).toBe(true);
970
+ expect(body["features"]).toEqual(["scim", "audit-logs"]);
971
+ expect(body["features"]).not.toContain(EnterpriseFeature.SSO);
618
972
  });
619
973
 
620
- it("tells a signed-in administrator how close the installation is to refusing users", async () => {
621
- (mockRequest as unknown as Record<string, unknown>)["userAuthorization"] =
622
- {
623
- userId: ObjectID.generate(),
624
- };
625
- UserService.countBy = jest
626
- .fn()
627
- .mockResolvedValue(new PositiveNumber(149));
974
+ it("is an empty list when the license entitles nothing", () => {
975
+ expect(
976
+ buildPublic(createLicenseSnapshotWithStatus("missing"))["features"],
977
+ ).toEqual([]);
978
+ });
628
979
 
629
- await callGet();
980
+ it("is null on the Community Edition, whatever snapshot is handed in", () => {
981
+ expect(buildPublic(null)["features"]).toBeNull();
982
+ expect(
983
+ buildPublic(createLicenseSnapshot(), false)["features"],
984
+ ).toBeNull();
985
+ });
630
986
 
631
- const body: JSONObject = getResponseBody();
987
+ it("hands out a copy, never the snapshot's own list", () => {
988
+ const snapshot: EnterpriseLicenseSnapshot = createLicenseSnapshot({
989
+ features: [EnterpriseFeature.SSO],
990
+ });
991
+ const features: Array<string> = buildPublic(snapshot)[
992
+ "features"
993
+ ] as Array<string>;
632
994
 
633
- expect(body["isSeatLimitEnforced"]).toBe(true);
634
- expect(body["seatsInUse"]).toBe(149);
635
- expect(body["seatsRemaining"]).toBe(1);
636
- expect(body["canAddMoreUsers"]).toBe(true);
995
+ features.push("mutated");
996
+
997
+ expect(snapshot.features).toEqual([EnterpriseFeature.SSO]);
637
998
  });
638
999
 
639
- /*
640
- * The same route serves the signed-out login page. How near this server is
641
- * to refusing new accounts is operational detail, and counting its users
642
- * for an anonymous visitor would be a free query on an unauthenticated
643
- * endpoint besides.
644
- */
645
- it("tells an anonymous visitor nothing about seat enforcement", async () => {
646
- await callGet();
1000
+ it("is the same for a master admin", () => {
1001
+ const body: JSONObject = GlobalConfigAPI.buildLicenseResponse({
1002
+ audience: "master-admin",
1003
+ isEnterpriseEditionLoaded: true,
1004
+ snapshot: createLicenseSnapshot({ features: [EnterpriseFeature.SSO] }),
1005
+ config: makeStoredConfig(),
1006
+ seatUsage: ENFORCED_SEAT_USAGE,
1007
+ });
647
1008
 
648
- const body: JSONObject = getResponseBody();
1009
+ expect(body["features"]).toEqual(["sso"]);
1010
+ });
1011
+ });
649
1012
 
650
- expect(body["isSeatLimitEnforced"]).toBe(false);
651
- expect(body["seatsInUse"]).toBeNull();
652
- expect(body["seatsRemaining"]).toBeNull();
653
- expect(body["canAddMoreUsers"]).toBe(true);
654
- expect(UserService.countBy).not.toHaveBeenCalled();
1013
+ it("maps unenforced seat usage to the 'nothing to enforce' fields", () => {
1014
+ expect(
1015
+ GlobalConfigAPI.getSeatUsageResponseFields({
1016
+ ...ENFORCED_SEAT_USAGE,
1017
+ isEnforced: false,
1018
+ }),
1019
+ ).toEqual({
1020
+ isSeatLimitEnforced: false,
1021
+ seatsInUse: null,
1022
+ seatsRemaining: null,
1023
+ canAddMoreUsers: true,
1024
+ });
1025
+ });
1026
+
1027
+ it("reports a full license as unable to take more users", () => {
1028
+ expect(
1029
+ GlobalConfigAPI.getSeatUsageResponseFields({
1030
+ ...ENFORCED_SEAT_USAGE,
1031
+ seatsInUse: 50,
1032
+ seatsRemaining: 0,
1033
+ hasSeatForNewUser: false,
1034
+ }),
1035
+ ).toEqual({
1036
+ isSeatLimitEnforced: true,
1037
+ seatsInUse: 50,
1038
+ seatsRemaining: 0,
1039
+ canAddMoreUsers: false,
655
1040
  });
656
1041
  });
657
1042
  });