@oneuptime/common 13.0.8 → 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 (333) 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/Scripts/generate-service-worker.js +65 -4
  6. package/Server/API/BaseAPI.ts +26 -5
  7. package/Server/API/GlobalConfigAPI.ts +228 -462
  8. package/Server/API/ProjectOIDC.ts +25 -16
  9. package/Server/API/ProjectSSO.ts +24 -16
  10. package/Server/API/StatusPageAPI.ts +100 -42
  11. package/Server/API/TeamMemberAPI.ts +10 -1
  12. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  13. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  14. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  15. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  16. package/Server/EnvironmentConfig.ts +147 -2
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  19. package/Server/Middleware/UserAuthorization.ts +38 -16
  20. package/Server/Services/AuditLogService.ts +84 -1079
  21. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  22. package/Server/Services/GlobalConfigService.ts +80 -1
  23. package/Server/Services/MonitorProbeService.ts +116 -20
  24. package/Server/Services/MonitorService.ts +56 -4
  25. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  26. package/Server/Services/MonitorTemplateService.ts +26 -0
  27. package/Server/Services/ProjectService.ts +284 -0
  28. package/Server/Services/RumSessionPinService.ts +25 -6
  29. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  30. package/Server/Services/StatusPageService.ts +78 -1
  31. package/Server/Services/TeamMemberService.ts +11 -0
  32. package/Server/Services/TeamService.ts +10 -0
  33. package/Server/Services/UserService.ts +10 -17
  34. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  35. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  36. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  37. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  38. package/Server/Types/Markdown.ts +41 -0
  39. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  40. package/Server/Utils/EditionEnforcement.ts +233 -0
  41. package/Server/Utils/FrontendEnvironment.ts +41 -2
  42. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  43. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  44. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  45. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  46. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  47. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  48. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  49. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  50. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  51. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  52. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  53. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  54. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  55. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  56. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  57. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  58. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  59. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  60. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  61. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  62. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  63. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  64. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  65. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  66. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  67. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  68. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  69. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  70. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  71. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  72. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  73. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  74. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  75. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  76. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  77. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  78. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  79. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  80. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  81. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  82. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  83. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  84. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  85. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  86. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  87. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  88. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  89. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  90. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  91. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  92. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  93. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  95. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  96. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  97. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  98. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  99. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  100. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  101. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  102. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  103. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  104. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  105. package/Tests/Server/Types/Markdown.test.ts +181 -0
  106. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  107. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  108. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  109. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  110. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  111. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  112. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  113. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  114. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  115. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  116. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  117. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  118. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  119. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  120. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  121. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  122. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  123. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  124. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  125. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  126. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  127. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  128. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  129. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  130. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  131. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  132. package/Types/API/Hostname.ts +63 -16
  133. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  134. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  135. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  136. package/Types/Database/CanonicalModelInstance.ts +102 -0
  137. package/Types/Database/TableColumn.ts +20 -6
  138. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  139. package/Types/IP/IPv6.ts +6 -1
  140. package/Types/Monitor/MonitorStep.ts +6 -1
  141. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  142. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  143. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  144. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  145. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  146. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  147. package/UI/Components/ModelList/ModelList.tsx +11 -1
  148. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  149. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  150. package/UI/esbuild-enterprise.js +263 -0
  151. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  152. package/Utils/HostAddressUtil.ts +94 -0
  153. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  154. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  155. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  156. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  157. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  159. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  161. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  163. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  164. package/build/dist/Server/API/BaseAPI.js +26 -5
  165. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  166. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  167. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  168. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  169. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  170. package/build/dist/Server/API/ProjectSSO.js +24 -16
  171. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  172. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  173. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  174. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  175. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  176. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  177. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  178. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  179. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  180. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  181. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  182. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  183. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  184. package/build/dist/Server/EnvironmentConfig.js +84 -2
  185. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  190. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  191. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  192. package/build/dist/Server/Services/AuditLogService.js +71 -740
  193. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  194. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  195. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  196. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  197. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  198. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  199. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  200. package/build/dist/Server/Services/MonitorService.js +45 -4
  201. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  202. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  203. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  204. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  205. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  206. package/build/dist/Server/Services/ProjectService.js +214 -0
  207. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  208. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  209. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  210. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  211. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  212. package/build/dist/Server/Services/StatusPageService.js +71 -0
  213. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  214. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  215. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  216. package/build/dist/Server/Services/TeamService.js +9 -0
  217. package/build/dist/Server/Services/TeamService.js.map +1 -1
  218. package/build/dist/Server/Services/UserService.js +10 -16
  219. package/build/dist/Server/Services/UserService.js.map +1 -1
  220. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  221. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  222. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  223. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  224. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  225. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  226. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  227. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  228. package/build/dist/Server/Types/Markdown.js +31 -0
  229. package/build/dist/Server/Types/Markdown.js.map +1 -1
  230. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  231. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  232. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  233. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  234. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  235. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  236. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  237. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  238. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  239. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  240. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  241. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  242. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  243. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  244. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  245. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  246. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  247. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  248. package/build/dist/Types/API/Hostname.js +59 -11
  249. package/build/dist/Types/API/Hostname.js.map +1 -1
  250. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  251. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  252. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  253. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  254. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  255. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  256. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  257. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  258. package/build/dist/Types/Database/TableColumn.js +19 -6
  259. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  260. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  261. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  262. package/build/dist/Types/IP/IPv6.js +6 -1
  263. package/build/dist/Types/IP/IPv6.js.map +1 -1
  264. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  265. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  266. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  267. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  268. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  269. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  270. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  271. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  272. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  273. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  274. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  275. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  276. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  277. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  278. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  279. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  280. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  281. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  282. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  283. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  284. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  285. package/build/dist/Utils/HostAddressUtil.js +80 -0
  286. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  287. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  288. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  289. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  290. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  291. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  292. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  293. package/jest.config.json +3 -1
  294. package/package.json +1 -1
  295. package/test-setup.sh +103 -34
  296. package/tsconfig.json +13 -0
  297. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  298. package/Server/API/TeamComplianceAPI.ts +0 -158
  299. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  300. package/Server/Services/TeamComplianceService.ts +0 -733
  301. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  302. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  303. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  304. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  305. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  306. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  307. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  308. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  309. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  310. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  311. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  312. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  313. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  314. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  315. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  316. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  317. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  318. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  319. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  320. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  321. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  322. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  323. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  324. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  325. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  326. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  327. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  328. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  329. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  330. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  331. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  332. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  333. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -21,6 +21,11 @@ import { MonitorFeedEventType } from "../../Models/DatabaseModels/MonitorFeed";
21
21
  import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
22
22
  import MonitorStatusService from "./MonitorStatusService";
23
23
  import ServerException from "../../Types/Exception/ServerException";
24
+ import {
25
+ MonitorUptimeDailyAggregate,
26
+ UptimeDailyAggregate,
27
+ UptimeDayBucket,
28
+ } from "../../Types/StatusPage/UptimeDailyAggregate";
24
29
 
25
30
  /*
26
31
  * Thrown by onBeforeCreate when the incoming status is the same as the status of
@@ -45,6 +50,285 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
45
50
  super(MonitorStatusTimeline);
46
51
  }
47
52
 
53
+ /**
54
+ * Per-monitor, per-day status durations for the status-page uptime bars.
55
+ *
56
+ * WHY THIS EXISTS
57
+ *
58
+ * The bars used to be painted from raw rows fetched with a single
59
+ * `limit: LIMIT_MAX` (10,000) across EVERY monitor on the page, sorted
60
+ * `startsAt DESC`. Measured on a real status page: 254,550 rows matched the
61
+ * 60-day window and 10,000 came back. The oldest surviving row started four
62
+ * days before the request, so 56 of 60 bars were painted from data that had
63
+ * been silently dropped - and the uptime percentage was computed from the
64
+ * same 3.9%.
65
+ *
66
+ * The cap being GLOBAL across monitors is what makes it vicious. A quiet,
67
+ * healthy monitor's covering row has an OLD `startsAt`, so under
68
+ * `startsAt DESC` it sorts last and is cut first: on the page that reported
69
+ * this, three flapping monitors held 9,995 of the 10,000 slots. The
70
+ * healthier a monitor is, the more likely it is to lose its entire history
71
+ * to a noisy neighbour on the same page.
72
+ *
73
+ * Shipping more rows cannot fix it - 254,550 rows is ~105 MB of this
74
+ * codebase's typed JSON, into a response held in a 500-entry in-memory
75
+ * cache and then filtered per-bar on the client's main thread. So this
76
+ * returns DURATIONS. The result is O(monitors x days x statuses) and cannot
77
+ * be inflated by a flapping monitor.
78
+ *
79
+ * THE SQL, AND THE THREE TRAPS IN IT
80
+ *
81
+ * 1. An open row (`endsAt IS NULL`) is capped at the NEXT row's `startsAt`,
82
+ * not at `now()`. `COALESCE(endsAt, now())` would overlap the successor
83
+ * and double-count that span. This mirrors what
84
+ * UptimeUtil.getMonitorEventsForId already does client-side.
85
+ *
86
+ * 2. Periods are split into days ARITHMETICALLY (`generate_series` over the
87
+ * days a period touches) and then hash-aggregated, rather than joined
88
+ * against a bucket grid. Both give the same answer; the join does not
89
+ * scale. Joining emitted one row per timeline row - 255,486 on the
90
+ * reporting page - which then had to be sorted for the GroupAggregate
91
+ * and spilled 26 MB to disk, ~4.7s. Splitting first aggregates 255k rows
92
+ * into ~450 with no sort: ~1.5s.
93
+ *
94
+ * A period that lies inside one day yields exactly one segment, which is
95
+ * the overwhelmingly common case - a flapping monitor's rows last well
96
+ * under a second. Only a long-running period fans out, one segment per
97
+ * day it spans.
98
+ *
99
+ * 3. Seconds are summed as `double precision` and NEVER rounded per group.
100
+ * Rounding each (monitor, day, status) sum to a bigint loses up to half a
101
+ * second per status, so a fully covered day with two statuses reported
102
+ * 86,399 of 86,400 seconds - a day that is complete looking one second
103
+ * short. Coverage is compared with a tolerance by the client rather than
104
+ * for exact equality.
105
+ *
106
+ * A trap that is worth recording even though this SQL no longer contains it:
107
+ * PostgreSQL's LEAST/GREATEST IGNORE NULLs, returning the smallest or
108
+ * largest NON-NULL argument. An earlier draft joined rows to buckets, and on
109
+ * an unmatched LEFT JOIN row `LEAST(NULL, bucketEnd) - GREATEST(NULL,
110
+ * bucketStart)` silently became a FULL DAY of coverage - reporting a monitor
111
+ * as up before it existed. It was caught against real data, not in review.
112
+ * Any future rewrite that reintroduces a row-to-bucket join must handle it.
113
+ *
114
+ * Day buckets are local calendar days converted to UTC instants, so a DST
115
+ * day is genuinely 23 or 25 hours - `daySeconds` is never hardcoded to
116
+ * 86400. The window's first and last buckets are clipped to the window, and
117
+ * the end is clamped to `now()` so a future day is never emitted and can
118
+ * never be reported as 100%.
119
+ */
120
+ @CaptureSpan()
121
+ public async getDailyUptimeAggregate(data: {
122
+ monitorIds: Array<ObjectID>;
123
+ startDate: Date;
124
+ endDate: Date;
125
+ timezone?: string | undefined;
126
+ }): Promise<UptimeDailyAggregate> {
127
+ const empty: UptimeDailyAggregate = {
128
+ monitors: [],
129
+ isComplete: true,
130
+ completeFrom: null,
131
+ };
132
+
133
+ if (data.monitorIds.length === 0) {
134
+ return empty;
135
+ }
136
+
137
+ /*
138
+ * The overlap predicate is preserved verbatim from the row fetch this
139
+ * replaces: a row counts if it started on or before the window ends and
140
+ * either ended on or after the window started or is still open. The
141
+ * inclusive `>=` is deliberate - a row ending exactly at startDate still
142
+ * overlaps - and a strict `>` is NOT interchangeable.
143
+ */
144
+ const sql: string = `
145
+ WITH params AS (
146
+ SELECT $2::timestamptz AS win_start,
147
+ LEAST($3::timestamptz, now()) AS eff_end,
148
+ $4::text AS tz
149
+ ),
150
+ mons AS (
151
+ SELECT UNNEST($1::uuid[]) AS monitor_id
152
+ ),
153
+ src AS (
154
+ SELECT t."monitorId" AS monitor_id,
155
+ t."monitorStatusId" AS status_id,
156
+ GREATEST(t."startsAt", p.win_start) AS s,
157
+ LEAST(
158
+ COALESCE(
159
+ t."endsAt",
160
+ LEAD(t."startsAt") OVER (
161
+ PARTITION BY t."monitorId" ORDER BY t."startsAt"
162
+ ),
163
+ p.eff_end
164
+ ),
165
+ p.eff_end
166
+ ) AS e
167
+ FROM "MonitorStatusTimeline" t
168
+ CROSS JOIN params p
169
+ WHERE t."monitorId" = ANY($1::uuid[])
170
+ AND t."deletedAt" IS NULL
171
+ AND t."startsAt" <= p.eff_end
172
+ AND (t."endsAt" >= p.win_start OR t."endsAt" IS NULL)
173
+ ),
174
+ split AS (
175
+ SELECT src.monitor_id,
176
+ src.status_id,
177
+ (gs AT TIME ZONE p.tz) AS day_start,
178
+ GREATEST(src.s, (gs AT TIME ZONE p.tz)) AS seg_start,
179
+ LEAST(src.e, ((gs + interval '1 day') AT TIME ZONE p.tz))
180
+ AS seg_end
181
+ FROM src
182
+ CROSS JOIN params p,
183
+ LATERAL generate_series(
184
+ date_trunc('day', src.s AT TIME ZONE p.tz),
185
+ date_trunc('day', (src.e - interval '1 microsecond') AT TIME ZONE p.tz),
186
+ interval '1 day'
187
+ ) AS gs
188
+ WHERE src.e > src.s
189
+ ),
190
+ agg AS (
191
+ SELECT monitor_id,
192
+ day_start,
193
+ status_id,
194
+ SUM(EXTRACT(EPOCH FROM (seg_end - seg_start)))::double precision
195
+ AS seconds
196
+ FROM split
197
+ WHERE seg_end > seg_start
198
+ GROUP BY monitor_id, day_start, status_id
199
+ ),
200
+ buckets AS (
201
+ SELECT (d AT TIME ZONE p.tz) AS day_start,
202
+ GREATEST((d AT TIME ZONE p.tz), p.win_start) AS bucket_start,
203
+ LEAST(((d + interval '1 day') AT TIME ZONE p.tz), p.eff_end)
204
+ AS bucket_end
205
+ FROM params p,
206
+ generate_series(
207
+ date_trunc('day', p.win_start AT TIME ZONE p.tz),
208
+ date_trunc('day', p.eff_end AT TIME ZONE p.tz),
209
+ interval '1 day'
210
+ ) AS d
211
+ )
212
+ SELECT m.monitor_id AS "monitorId",
213
+ b.bucket_start AS "bucketStart",
214
+ b.bucket_end AS "bucketEnd",
215
+ EXTRACT(EPOCH FROM (b.bucket_end - b.bucket_start))::bigint
216
+ AS "daySeconds",
217
+ a.status_id AS "monitorStatusId",
218
+ COALESCE(a.seconds, 0)::double precision AS "seconds"
219
+ FROM mons m
220
+ CROSS JOIN buckets b
221
+ LEFT JOIN agg a ON a.monitor_id = m.monitor_id
222
+ AND a.day_start = b.day_start
223
+ WHERE b.bucket_end > b.bucket_start
224
+ ORDER BY m.monitor_id, b.bucket_start
225
+ `;
226
+
227
+ const rows: Array<{
228
+ monitorId: string;
229
+ bucketStart: Date;
230
+ bucketEnd: Date;
231
+ daySeconds: string | number;
232
+ monitorStatusId: string | null;
233
+ seconds: string | number;
234
+ }> = await this.getRepository().manager.query(sql, [
235
+ data.monitorIds.map((id: ObjectID) => {
236
+ return id.toString();
237
+ }),
238
+ data.startDate,
239
+ data.endDate,
240
+ data.timezone || "UTC",
241
+ ]);
242
+
243
+ return Service.toUptimeDailyAggregate(rows);
244
+ }
245
+
246
+ /*
247
+ * Exported shape-only step, so the bucketing can be tested without a
248
+ * database. One row per (monitor, day, status); a day with no coverage
249
+ * comes back as a single row with a NULL status and zero seconds, which is
250
+ * what makes a no-data day distinguishable from a day spent up.
251
+ */
252
+ public static toUptimeDailyAggregate(
253
+ rows: Array<{
254
+ monitorId: string;
255
+ bucketStart: Date;
256
+ bucketEnd: Date;
257
+ daySeconds: string | number;
258
+ monitorStatusId: string | null;
259
+ seconds: string | number;
260
+ }>,
261
+ ): UptimeDailyAggregate {
262
+ const byMonitor: Map<string, Map<number, UptimeDayBucket>> = new Map();
263
+
264
+ for (const row of rows) {
265
+ let buckets: Map<number, UptimeDayBucket> | undefined = byMonitor.get(
266
+ row.monitorId,
267
+ );
268
+
269
+ if (!buckets) {
270
+ buckets = new Map<number, UptimeDayBucket>();
271
+ byMonitor.set(row.monitorId, buckets);
272
+ }
273
+
274
+ const bucketStart: Date = new Date(row.bucketStart);
275
+ const key: number = bucketStart.getTime();
276
+
277
+ let bucket: UptimeDayBucket | undefined = buckets.get(key);
278
+
279
+ if (!bucket) {
280
+ bucket = {
281
+ bucketStart: bucketStart,
282
+ bucketEnd: new Date(row.bucketEnd),
283
+ daySeconds: Number(row.daySeconds),
284
+ coveredSeconds: 0,
285
+ statusDurations: [],
286
+ };
287
+ buckets.set(key, bucket);
288
+ }
289
+
290
+ const seconds: number = Number(row.seconds);
291
+
292
+ /*
293
+ * A NULL status is the LEFT JOIN's "nothing overlapped this day" row.
294
+ * It must not become a status duration, and it contributes no coverage.
295
+ */
296
+ if (!row.monitorStatusId || seconds <= 0) {
297
+ continue;
298
+ }
299
+
300
+ bucket.coveredSeconds += seconds;
301
+ bucket.statusDurations.push({
302
+ monitorStatusId: new ObjectID(row.monitorStatusId),
303
+ seconds: seconds,
304
+ });
305
+ }
306
+
307
+ const monitors: Array<MonitorUptimeDailyAggregate> = [];
308
+
309
+ for (const [monitorId, buckets] of byMonitor) {
310
+ monitors.push({
311
+ monitorId: new ObjectID(monitorId),
312
+ buckets: Array.from(buckets.values()).sort(
313
+ (a: UptimeDayBucket, b: UptimeDayBucket) => {
314
+ return a.bucketStart.getTime() - b.bucketStart.getTime();
315
+ },
316
+ ),
317
+ });
318
+ }
319
+
320
+ /*
321
+ * There is no cap in this path, so the window is always fully covered.
322
+ * The fields exist so that a bound added later has to SHOW rather than be
323
+ * absorbed into a green bar - which is the failure this replaces.
324
+ */
325
+ return {
326
+ monitors: monitors,
327
+ isComplete: true,
328
+ completeFrom: null,
329
+ };
330
+ }
331
+
48
332
  @CaptureSpan()
49
333
  public override async create(
50
334
  createBy: CreateBy<MonitorStatusTimeline>,
@@ -10,6 +10,7 @@ import DeleteBy from "../Types/Database/DeleteBy";
10
10
  import UpdateBy from "../Types/Database/UpdateBy";
11
11
  import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
12
12
  import MonitorStepsProjectValidator from "../Utils/Monitor/MonitorStepsProjectValidator";
13
+ import MonitoringIntervalValidator from "../Utils/Monitor/MonitoringIntervalValidator";
13
14
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
14
15
  import BadDataException from "../../Types/Exception/BadDataException";
15
16
  import Includes from "../../Types/BaseDatabase/Includes";
@@ -116,6 +117,18 @@ export class Service extends DatabaseService<Model> {
116
117
  protected override async onBeforeCreate(
117
118
  createBy: CreateBy<Model>,
118
119
  ): Promise<OnCreate<Model>> {
120
+ /*
121
+ * A template's interval is copied onto every monitor it creates or syncs,
122
+ * so an unvalidated value here becomes many broken monitors later.
123
+ */
124
+ if (createBy.data.monitoringInterval !== undefined) {
125
+ (createBy.data as unknown as Record<string, unknown>)[
126
+ "monitoringInterval"
127
+ ] = MonitoringIntervalValidator.validateAndNormalize(
128
+ createBy.data.monitoringInterval,
129
+ );
130
+ }
131
+
119
132
  this.validateSyncFields(
120
133
  createBy.data.monitorSteps,
121
134
  createBy.data.monitorType,
@@ -132,6 +145,19 @@ export class Service extends DatabaseService<Model> {
132
145
  protected override async onBeforeUpdate(
133
146
  updateBy: UpdateBy<Model>,
134
147
  ): Promise<OnUpdate<Model>> {
148
+ /*
149
+ * Ahead of the early return below: an update that touches only the
150
+ * interval must still be validated, and key presence is the test for the
151
+ * same reason it is in MonitorService.onBeforeUpdate.
152
+ */
153
+ if (Object.keys(updateBy.data || {}).includes("monitoringInterval")) {
154
+ (updateBy.data as unknown as Record<string, unknown>)[
155
+ "monitoringInterval"
156
+ ] = MonitoringIntervalValidator.validateAndNormalize(
157
+ updateBy.data.monitoringInterval as string | null | undefined,
158
+ );
159
+ }
160
+
135
161
  if (!updateBy.data.monitorSteps && !updateBy.data.monitorType) {
136
162
  return { updateBy, carryForward: null };
137
163
  }
@@ -14,6 +14,9 @@ import { OnCreate, OnDelete, OnFind, OnUpdate } from "../Types/Database/Hooks";
14
14
  import QueryHelper from "../Types/Database/QueryHelper";
15
15
  import UpdateBy from "../Types/Database/UpdateBy";
16
16
  import logger, { LogAttributes } from "../Utils/Logger";
17
+ import EditionEnforcement from "../Utils/EditionEnforcement";
18
+ import EnterpriseEdition from "../Enterprise/EnterpriseEdition";
19
+ import EnterpriseFeature from "../Enterprise/EnterpriseFeature";
17
20
  import Errors from "../Utils/Errors";
18
21
  import ProductAnalytics from "../Utils/ProductAnalytics";
19
22
  import MarketingEventUtil, {
@@ -142,6 +145,107 @@ export type ProjectBalanceColumnName =
142
145
  */
143
146
  export const MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS: number = 10_000 * 100;
144
147
 
148
+ /*
149
+ * The project columns that hold its SSO requirement: the ones onFindSuccess
150
+ * masks and onBeforeUpdate guards while SSO is not active.
151
+ */
152
+ export const PROJECT_SSO_REQUIREMENT_COLUMNS: ReadonlyArray<string> = [
153
+ "requireSsoForLogin",
154
+ "requireSsoWithSsoProviderId",
155
+ ];
156
+
157
+ // The project columns that decide what the audit log records and keeps.
158
+ export interface ProjectAuditLogSettings {
159
+ enableAuditLogs: boolean;
160
+ storeSystemEventsInAuditLogs: boolean;
161
+ auditLogsRetentionInDays: number;
162
+ }
163
+
164
+ /*
165
+ * The settings as a project row holds them: a column that was not selected,
166
+ * or predates its default, reads as undefined or null.
167
+ */
168
+ export interface StoredProjectAuditLogSettings {
169
+ enableAuditLogs?: boolean | null | undefined;
170
+ storeSystemEventsInAuditLogs?: boolean | null | undefined;
171
+ auditLogsRetentionInDays?: number | null | undefined;
172
+ }
173
+
174
+ // What a new project starts with: the three columns' defaults.
175
+ export const DEFAULT_PROJECT_AUDIT_LOG_SETTINGS: Readonly<ProjectAuditLogSettings> =
176
+ {
177
+ enableAuditLogs: false,
178
+ storeSystemEventsInAuditLogs: false,
179
+ auditLogsRetentionInDays: 7,
180
+ };
181
+
182
+ /*
183
+ * The narrowest settings there are. A write that widens even these could
184
+ * widen some project's; one that does not can only keep or narrow.
185
+ */
186
+ const NARROWEST_AUDIT_LOG_SETTINGS: Readonly<ProjectAuditLogSettings> = {
187
+ enableAuditLogs: false,
188
+ storeSystemEventsInAuditLogs: false,
189
+ auditLogsRetentionInDays: Number.NEGATIVE_INFINITY,
190
+ };
191
+
192
+ /*
193
+ * Whether a written switch value turns the switch on. Only false (and a
194
+ * missing value) means off: anything else a database could read as true
195
+ * ("true", 1) counts as on, so an odd value is judged by the license check
196
+ * rather than waved through.
197
+ */
198
+ const isSwitchedOn: (value: unknown) => boolean = (value: unknown): boolean => {
199
+ return value !== undefined && value !== null && value !== false;
200
+ };
201
+
202
+ /*
203
+ * Whether writing `requested` over `current` widens audit logging: switches
204
+ * it on, starts recording system events, or keeps entries for longer.
205
+ * Switching off, recording less and shortening retention never widen, and
206
+ * neither does writing the value a project already has.
207
+ */
208
+ export const widensAuditLogging: (
209
+ requested: Record<string, unknown>,
210
+ current: StoredProjectAuditLogSettings,
211
+ ) => boolean = (
212
+ requested: Record<string, unknown>,
213
+ current: StoredProjectAuditLogSettings,
214
+ ): boolean => {
215
+ if (
216
+ isSwitchedOn(requested["enableAuditLogs"]) &&
217
+ current.enableAuditLogs !== true
218
+ ) {
219
+ return true;
220
+ }
221
+
222
+ if (
223
+ isSwitchedOn(requested["storeSystemEventsInAuditLogs"]) &&
224
+ current.storeSystemEventsInAuditLogs !== true
225
+ ) {
226
+ return true;
227
+ }
228
+
229
+ const requestedRetention: unknown = requested["auditLogsRetentionInDays"];
230
+
231
+ if (requestedRetention === undefined || requestedRetention === null) {
232
+ return false;
233
+ }
234
+
235
+ const requestedDays: number =
236
+ typeof requestedRetention === "number"
237
+ ? requestedRetention
238
+ : Number(requestedRetention);
239
+
240
+ const currentDays: number =
241
+ typeof current.auditLogsRetentionInDays === "number"
242
+ ? current.auditLogsRetentionInDays
243
+ : DEFAULT_PROJECT_AUDIT_LOG_SETTINGS.auditLogsRetentionInDays;
244
+
245
+ // A value that is not a number cannot be judged: treat it as widening.
246
+ return !Number.isFinite(requestedDays) || requestedDays > currentDays;
247
+ };
248
+
145
249
  export class ProjectService extends DatabaseService<Model> {
146
250
  /*
147
251
  * Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
@@ -231,6 +335,17 @@ export class ProjectService extends DatabaseService<Model> {
231
335
  );
232
336
  }
233
337
 
338
+ // A new project starts from the defaults, so any audit setting above them widens.
339
+ await this.assertAuditLogSettingsChangeIsLicensed({
340
+ requested: data.data as unknown as Record<string, unknown>,
341
+ props: data.props,
342
+ loadCurrentSettings: async (): Promise<
343
+ Array<StoredProjectAuditLogSettings>
344
+ > => {
345
+ return [DEFAULT_PROJECT_AUDIT_LOG_SETTINGS];
346
+ },
347
+ });
348
+
234
349
  logger.debug("Creating project for user " + data.props.userId, {
235
350
  userId: data.props.userId?.toString(),
236
351
  } as LogAttributes);
@@ -546,6 +661,17 @@ export class ProjectService extends DatabaseService<Model> {
546
661
  protected override async onBeforeUpdate(
547
662
  updateBy: UpdateBy<Model>,
548
663
  ): Promise<OnUpdate<Model>> {
664
+ /*
665
+ * While SSO is not active this caller only ever read the requirement as
666
+ * off (see onFindSuccess), so a write of it must not switch the stored
667
+ * requirement off, or set one nobody could see. Drops or refuses it.
668
+ */
669
+ EditionEnforcement.guardSsoRequirementWrite({
670
+ props: updateBy.props,
671
+ data: updateBy.data as unknown as Record<string, unknown>,
672
+ columns: PROJECT_SSO_REQUIREMENT_COLUMNS,
673
+ });
674
+
549
675
  /*
550
676
  * Any project field could have changed; invalidate the in-process cache
551
677
  * of the SSO flag. Cheap to refetch on the next request.
@@ -560,6 +686,19 @@ export class ProjectService extends DatabaseService<Model> {
560
686
 
561
687
  this.applyDataResidencyRules(updateBy.data);
562
688
 
689
+ await this.assertAuditLogSettingsChangeIsLicensed({
690
+ requested: updateBy.data as unknown as Record<string, unknown>,
691
+ props: updateBy.props,
692
+ loadCurrentSettings: (): Promise<
693
+ Array<StoredProjectAuditLogSettings>
694
+ > => {
695
+ return this.findAuditLogSettingsInCallerScope({
696
+ query: updateBy.query,
697
+ props: updateBy.props,
698
+ });
699
+ },
700
+ });
701
+
563
702
  if (IsBillingEnabled) {
564
703
  if (
565
704
  updateBy.data.businessDetails ||
@@ -653,6 +792,113 @@ export class ProjectService extends DatabaseService<Model> {
653
792
  return { updateBy, carryForward: [] };
654
793
  }
655
794
 
795
+ /*
796
+ * Audit logging is an Enterprise feature (EnterpriseFeature.AuditLogs), but
797
+ * its settings are ordinary Project columns, gated only by the plan when
798
+ * billing is on. Without billing, switching audit logging on - or widening
799
+ * it: recording system events too, keeping entries longer - needs a license
800
+ * that includes audit logs, like creating or changing any other enterprise
801
+ * configuration. On the Community Edition it is refused.
802
+ *
803
+ * Never refused: switching it off or narrowing it (an administrator must
804
+ * always be able to record less), a write that keeps a project's settings
805
+ * as they are (the settings form sends every field), internal root writes,
806
+ * and anything when billing is on, where the plan gates apply. Whether
807
+ * entries are recorded at all is decided per entry by the recorder
808
+ * (EnterpriseEdition.isFeatureActive(AuditLogs)): recording stops while a
809
+ * self-hosted license is lapsed and resumes when it is renewed.
810
+ *
811
+ * The current settings are loaded only when the write could widen them and
812
+ * the license does not allow it.
813
+ */
814
+ @CaptureSpan()
815
+ public async assertAuditLogSettingsChangeIsLicensed(data: {
816
+ requested: Record<string, unknown>;
817
+ props: DatabaseCommonInteractionProps;
818
+ // The settings the write replaces, one entry per project it writes.
819
+ loadCurrentSettings: () => Promise<Array<StoredProjectAuditLogSettings>>;
820
+ }): Promise<void> {
821
+ if (IsBillingEnabled || data.props.isRoot) {
822
+ return;
823
+ }
824
+
825
+ if (!widensAuditLogging(data.requested, NARROWEST_AUDIT_LOG_SETTINGS)) {
826
+ return;
827
+ }
828
+
829
+ if (
830
+ await EnterpriseEdition.isFeatureAvailable(EnterpriseFeature.AuditLogs)
831
+ ) {
832
+ return;
833
+ }
834
+
835
+ const currentSettings: Array<StoredProjectAuditLogSettings> =
836
+ await data.loadCurrentSettings();
837
+
838
+ const widens: boolean = currentSettings.some(
839
+ (current: StoredProjectAuditLogSettings): boolean => {
840
+ return widensAuditLogging(data.requested, current);
841
+ },
842
+ );
843
+
844
+ if (!widens) {
845
+ return;
846
+ }
847
+
848
+ // Throws the Community Edition or the license message, as the case may be.
849
+ await EnterpriseEdition.assertFeatureAvailable(EnterpriseFeature.AuditLogs);
850
+ }
851
+
852
+ /*
853
+ * The audit settings of the projects an update matches, limited to the
854
+ * projects the caller belongs to (every project for a master admin). This
855
+ * runs in onBeforeUpdate, before the permission check: a refusal that
856
+ * depended on another project's settings would disclose them, and a write
857
+ * to a project the caller does not belong to is refused by the permission
858
+ * check that follows anyway.
859
+ */
860
+ private async findAuditLogSettingsInCallerScope(data: {
861
+ query: Query<Model>;
862
+ props: DatabaseCommonInteractionProps;
863
+ }): Promise<Array<StoredProjectAuditLogSettings>> {
864
+ const projects: Array<Model> = await this.findBy({
865
+ query: data.query,
866
+ select: {
867
+ _id: true,
868
+ enableAuditLogs: true,
869
+ storeSystemEventsInAuditLogs: true,
870
+ auditLogsRetentionInDays: true,
871
+ },
872
+ limit: LIMIT_MAX,
873
+ skip: 0,
874
+ props: { isRoot: true },
875
+ });
876
+
877
+ const callerProjectIds: Array<string> = data.props.isMasterAdmin
878
+ ? []
879
+ : [
880
+ ...(data.props.tenantId ? [data.props.tenantId] : []),
881
+ ...(data.props.userGlobalAccessPermission?.projectIds || []),
882
+ ].map((projectId: ObjectID): string => {
883
+ return projectId.toString();
884
+ });
885
+
886
+ return projects
887
+ .filter((project: Model): boolean => {
888
+ return (
889
+ Boolean(data.props.isMasterAdmin) ||
890
+ callerProjectIds.includes(String(project._id))
891
+ );
892
+ })
893
+ .map((project: Model): StoredProjectAuditLogSettings => {
894
+ return {
895
+ enableAuditLogs: project.enableAuditLogs,
896
+ storeSystemEventsInAuditLogs: project.storeSystemEventsInAuditLogs,
897
+ auditLogsRetentionInDays: project.auditLogsRetentionInDays,
898
+ };
899
+ });
900
+ }
901
+
656
902
  @CaptureSpan()
657
903
  public async changePlan(params: {
658
904
  projectId: ObjectID;
@@ -2570,6 +2816,44 @@ These are no longer recorded against the project and have to be cancelled by han
2570
2816
  return { findBy, carryForward: null };
2571
2817
  }
2572
2818
 
2819
+ /*
2820
+ * While SSO is not active a project's SSO requirement is not enforced: on
2821
+ * the Community Edition (the SSO login routes are part of the Enterprise
2822
+ * Edition), and on an Enterprise install whose license is lapsed or does
2823
+ * not include SSO (the routes refuse). Reads made for a caller then report
2824
+ * the EFFECTIVE value: not required, no required provider. Clients decide
2825
+ * from these columns whether to start an SSO flow - the mobile app hides a
2826
+ * project's on-call data behind an SSO login it cannot complete, and its
2827
+ * store builds cannot be patched.
2828
+ *
2829
+ * Only the returned objects change. Internal (root) reads, and the stored
2830
+ * row, keep the real value, and onBeforeUpdate keeps a caller from writing
2831
+ * the masked value back. So running the Enterprise Edition with a valid
2832
+ * license (or in its trial or grace period) again restores enforcement
2833
+ * exactly as configured.
2834
+ */
2835
+ @CaptureSpan()
2836
+ protected override async onFindSuccess(
2837
+ onFind: OnFind<Model>,
2838
+ items: Array<Model>,
2839
+ ): Promise<OnFind<Model>> {
2840
+ if (
2841
+ EditionEnforcement.shouldMaskSsoRequirementOnRead(onFind.findBy.props)
2842
+ ) {
2843
+ for (const item of items) {
2844
+ if (item.requireSsoForLogin !== undefined) {
2845
+ item.requireSsoForLogin = false;
2846
+ }
2847
+
2848
+ if (item.requireSsoWithSsoProviderId !== undefined) {
2849
+ item.requireSsoWithSsoProviderId = null!;
2850
+ }
2851
+ }
2852
+ }
2853
+
2854
+ return { ...onFind, carryForward: items };
2855
+ }
2856
+
2573
2857
  @CaptureSpan()
2574
2858
  protected override async onBeforeDelete(
2575
2859
  deleteBy: DeleteBy<Model>,