@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
@@ -162,12 +162,31 @@ export class Service extends DatabaseService<Model> {
162
162
  this.assertCallerMayCreatePins(createBy.props);
163
163
 
164
164
  if (!isInternalCaller(createBy.props)) {
165
- /* The database keeps these for itself; a request does not set them. */
166
- delete createBy.data._id;
167
- delete createBy.data.createdAt;
168
- delete createBy.data.updatedAt;
169
- delete createBy.data.deletedAt;
170
- delete createBy.data.version;
165
+ /*
166
+ * The database keeps these for itself; a request does not set them.
167
+ *
168
+ * Cleared rather than deleted: `createBy.data` is a live model instance,
169
+ * and the column-metadata lookups downstream used to enumerate the own
170
+ * keys of whichever instance reached them first and cache that as the
171
+ * CLASS's column list - so deleting `_id` off this instance taught the
172
+ * whole process that RumSessionPin has no id, and every later response
173
+ * for it lost one. TableColumn.ts no longer reads the caller's instance
174
+ * (see CanonicalModelInstance.ts), so this is defence in depth. An
175
+ * unset column holds `undefined` anyway, which TypeORM and the write
176
+ * path treat exactly as an absent key - the save is still an INSERT.
177
+ */
178
+ const databaseManagedColumns: Array<string> = [
179
+ "_id",
180
+ "createdAt",
181
+ "updatedAt",
182
+ "deletedAt",
183
+ "version",
184
+ ];
185
+
186
+ for (const columnName of databaseManagedColumns) {
187
+ (createBy.data as unknown as Record<string, unknown>)[columnName] =
188
+ undefined;
189
+ }
171
190
  }
172
191
 
173
192
  const pinKey: PinKey | null = this.resolvePinKey(createBy);
@@ -1,8 +1,10 @@
1
+ import crypto from "crypto";
1
2
  import DatabaseConfig from "../DatabaseConfig";
2
3
  import { EncryptionSecret } from "../EnvironmentConfig";
3
4
  import CreateBy from "../Types/Database/CreateBy";
4
5
  import UpdateBy from "../Types/Database/UpdateBy";
5
6
  import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
7
+ import EditionEnforcement from "../Utils/EditionEnforcement";
6
8
  import logger from "../Utils/Logger";
7
9
  import DatabaseService from "./DatabaseService";
8
10
  import MailService from "./MailService";
@@ -19,7 +21,6 @@ import LIMIT_MAX from "../../Types/Database/LimitMax";
19
21
  import Email from "../../Types/Email";
20
22
  import HashedString from "../../Types/HashedString";
21
23
  import BadDataException from "../../Types/Exception/BadDataException";
22
- import ObjectID from "../../Types/ObjectID";
23
24
  import StatusPage from "../../Models/DatabaseModels/StatusPage";
24
25
  import Model from "../../Models/DatabaseModels/StatusPagePrivateUser";
25
26
 
@@ -189,8 +190,10 @@ export class Service extends DatabaseService<Model> {
189
190
  /*
190
191
  * SCIM and admin-created users must follow the page's login policy too.
191
192
  * Do not create a password-reset credential when only SSO can be used.
193
+ * On the Community Edition there is no SSO login for status pages, so a
194
+ * leftover requirement is not enforced and the user gets a password link.
192
195
  */
193
- if (statusPage.requireSsoForLogin) {
196
+ if (EditionEnforcement.isSsoRequired(statusPage.requireSsoForLogin)) {
194
197
  return createdItem;
195
198
  }
196
199
 
@@ -206,7 +209,8 @@ export class Service extends DatabaseService<Model> {
206
209
  * Same split as `/status-page-api/forgot-password`: the digest is
207
210
  * persisted, the raw token is only ever mailed.
208
211
  */
209
- const token: string = ObjectID.generate().toString();
212
+ // A bearer secret: from the CSPRNG, never ObjectID's non-crypto fallback.
213
+ const token: string = crypto.randomUUID();
210
214
  const hashedToken: string = await HashedString.hashValue(
211
215
  token,
212
216
  EncryptionSecret,
@@ -1,9 +1,10 @@
1
1
  import DatabaseConfig from "../DatabaseConfig";
2
2
  import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
3
3
  import CreateBy from "../Types/Database/CreateBy";
4
- import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
4
+ import { OnCreate, OnFind, OnUpdate } from "../Types/Database/Hooks";
5
5
  import UpdateBy from "../Types/Database/UpdateBy";
6
6
  import CookieUtil from "../Utils/Cookie";
7
+ import EditionEnforcement from "../Utils/EditionEnforcement";
7
8
  import { ExpressRequest } from "../Utils/Express";
8
9
  import JSONWebToken from "../Utils/JsonWebToken";
9
10
  import logger, { LogAttributes } from "../Utils/Logger";
@@ -65,6 +66,7 @@ import OneUptimeDate from "../../Types/Date";
65
66
  import IncidentService from "./IncidentService";
66
67
  import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
67
68
  import MonitorStatusTimelineService from "./MonitorStatusTimelineService";
69
+ import { UptimeDailyAggregate } from "../../Types/StatusPage/UptimeDailyAggregate";
68
70
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
69
71
  import UptimeUtil, { UptimeWindow } from "../../Utils/Uptime/UptimeUtil";
70
72
  import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
@@ -104,6 +106,14 @@ export {
104
106
  StatusPageReportRow,
105
107
  };
106
108
 
109
+ /*
110
+ * The status page column that holds its SSO requirement: the one
111
+ * onFindSuccess masks and onBeforeUpdate guards while SSO is not active.
112
+ */
113
+ export const STATUS_PAGE_SSO_REQUIREMENT_COLUMNS: ReadonlyArray<string> = [
114
+ "requireSsoForLogin",
115
+ ];
116
+
107
117
  export class Service extends DatabaseService<StatusPage> {
108
118
  /*
109
119
  * Caches the resolved status page URL per statusPageId. `getStatusPageURL`
@@ -268,6 +278,36 @@ export class Service extends DatabaseService<StatusPage> {
268
278
  return this.getDefaultEmailFooterText();
269
279
  }
270
280
 
281
+ /*
282
+ * While SSO is not active a status page's SSO requirement is not enforced:
283
+ * on the Community Edition (status page SSO login is part of the
284
+ * Enterprise Edition), and on an Enterprise install whose license is
285
+ * lapsed or does not include SSO (the SSO routes refuse). Reads made for a
286
+ * caller then report the EFFECTIVE value: not required. Internal (root)
287
+ * reads and the stored row keep the real value, and onBeforeUpdate keeps a
288
+ * caller from writing the masked value back, so running the Enterprise
289
+ * Edition with a valid license (or in its trial or grace period) again
290
+ * restores enforcement exactly as configured. The public master-page route
291
+ * reads as root and applies the same rule itself.
292
+ */
293
+ @CaptureSpan()
294
+ protected override async onFindSuccess(
295
+ onFind: OnFind<StatusPage>,
296
+ items: Array<StatusPage>,
297
+ ): Promise<OnFind<StatusPage>> {
298
+ if (
299
+ EditionEnforcement.shouldMaskSsoRequirementOnRead(onFind.findBy.props)
300
+ ) {
301
+ for (const item of items) {
302
+ if (item.requireSsoForLogin !== undefined) {
303
+ item.requireSsoForLogin = false;
304
+ }
305
+ }
306
+ }
307
+
308
+ return { ...onFind, carryForward: items };
309
+ }
310
+
271
311
  @CaptureSpan()
272
312
  protected override async onBeforeCreate(
273
313
  createBy: CreateBy<StatusPage>,
@@ -763,6 +803,32 @@ export class Service extends DatabaseService<StatusPage> {
763
803
  return false;
764
804
  }
765
805
 
806
+ /**
807
+ * Per-monitor, per-day status durations for this status page's bars.
808
+ *
809
+ * This is what the uptime bars are painted from. It exists because the raw
810
+ * row fetch below is capped at LIMIT_MAX across EVERY monitor on the page,
811
+ * sorted newest-first - a bound that silently destroyed history. Measured
812
+ * on a real page: 254,550 rows matched the window, 10,000 came back, and
813
+ * three flapping monitors held 9,995 of those slots, starving the quiet
814
+ * monitors out entirely. See MonitorStatusTimelineService for the full
815
+ * account and for the traps in the SQL.
816
+ */
817
+ @CaptureSpan()
818
+ public async getUptimeDailyAggregateForStatusPage(data: {
819
+ monitorIds: Array<ObjectID>;
820
+ startDate: Date;
821
+ endDate: Date;
822
+ timezone?: string | undefined;
823
+ }): Promise<UptimeDailyAggregate> {
824
+ return await MonitorStatusTimelineService.getDailyUptimeAggregate({
825
+ monitorIds: data.monitorIds,
826
+ startDate: data.startDate,
827
+ endDate: data.endDate,
828
+ timezone: data.timezone,
829
+ });
830
+ }
831
+
766
832
  @CaptureSpan()
767
833
  public async getMonitorStatusTimelineForStatusPage(data: {
768
834
  monitorIds: Array<ObjectID>;
@@ -991,6 +1057,17 @@ export class Service extends DatabaseService<StatusPage> {
991
1057
  protected override async onBeforeUpdate(
992
1058
  updateBy: UpdateBy<StatusPage>,
993
1059
  ): Promise<OnUpdate<StatusPage>> {
1060
+ /*
1061
+ * While SSO is not active this caller only ever read the requirement as
1062
+ * off (see onFindSuccess), so a write of it must not switch the stored
1063
+ * requirement off, or set one nobody could see. Drops or refuses it.
1064
+ */
1065
+ EditionEnforcement.guardSsoRequirementWrite({
1066
+ props: updateBy.props,
1067
+ data: updateBy.data as unknown as Record<string, unknown>,
1068
+ columns: STATUS_PAGE_SSO_REQUIREMENT_COLUMNS,
1069
+ });
1070
+
994
1071
  // is enabling SMS subscribers.
995
1072
 
996
1073
  if (updateBy.data.enableSmsSubscribers) {
@@ -8,6 +8,7 @@ import QueryHelper from "../Types/Database/QueryHelper";
8
8
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
9
9
  import Select from "../Types/Database/Select";
10
10
  import UpdateBy from "../Types/Database/UpdateBy";
11
+ import EditionEnforcement from "../Utils/EditionEnforcement";
11
12
  import Errors from "../Utils/Errors";
12
13
  import logger, { LogAttributes } from "../Utils/Logger";
13
14
  import ProductAnalytics from "../Utils/ProductAnalytics";
@@ -89,8 +90,18 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
89
90
  super(TeamMember);
90
91
  }
91
92
 
93
+ /*
94
+ * Whether SCIM Push Groups owns this project's team membership, which locks
95
+ * member invites and removals made from OneUptime and lifts the one-member
96
+ * guard. Never on the Community Edition: it serves no SCIM endpoint, so a
97
+ * leftover Push Groups setting there would leave teams nobody can manage.
98
+ */
92
99
  @CaptureSpan()
93
100
  private async isSCIMPushGroupsEnabled(projectId: ObjectID): Promise<boolean> {
101
+ if (!EditionEnforcement.areScimTeamLocksEnforced()) {
102
+ return false;
103
+ }
104
+
94
105
  const count: PositiveNumber = await ProjectSCIMService.countBy({
95
106
  query: {
96
107
  projectId: projectId,
@@ -14,6 +14,7 @@ import TeamMember from "../../Models/DatabaseModels/TeamMember";
14
14
  import TeamMemberService from "./TeamMemberService";
15
15
  import ProjectSCIMService from "./ProjectSCIMService";
16
16
  import ModelPermission from "../Types/Database/Permissions/Index";
17
+ import EditionEnforcement from "../Utils/EditionEnforcement";
17
18
 
18
19
  export class Service extends DatabaseService<Model> {
19
20
  public constructor() {
@@ -61,6 +62,15 @@ export class Service extends DatabaseService<Model> {
61
62
  return;
62
63
  }
63
64
 
65
+ /*
66
+ * The lock exists because the identity provider owns the teams while
67
+ * Push Groups is on. The Community Edition serves no SCIM endpoint, so
68
+ * nothing could manage them there: a leftover lock is not enforced.
69
+ */
70
+ if (!EditionEnforcement.areScimTeamLocksEnforced()) {
71
+ return;
72
+ }
73
+
64
74
  const uniqueProjectIds: Map<string, ObjectID> = new Map();
65
75
 
66
76
  for (const projectId of data.projectIds) {
@@ -1,3 +1,4 @@
1
+ import crypto from "crypto";
1
2
  import DatabaseConfig from "../DatabaseConfig";
2
3
  import {
3
4
  EncryptionSecret,
@@ -59,7 +60,7 @@ import Name from "../../Types/Name";
59
60
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
60
61
  import Timezone from "../../Types/Timezone";
61
62
  import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
62
- import EnterpriseLicenseSeatUtil from "../Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil";
63
+ import EnterpriseEdition from "../Enterprise/EnterpriseEdition";
63
64
 
64
65
  /*
65
66
  * Names the Redis mutex that serializes the first-Master-Admin election across
@@ -195,22 +196,13 @@ export class Service extends DatabaseService<Model> {
195
196
  *
196
197
  * No isRoot exemption on purpose — invitations create the invited user as
197
198
  * root, so exempting root would exempt invitations, which is the exact
198
- * thing this is here to bound. It is a no-op on Community Edition and on
199
- * oneuptime.com (see EnterpriseLicenseSeatUtil.isSeatLimitEnforceable),
200
- * and it never counts the User table on a licence with no seat limit.
199
+ * thing this is here to bound. The limit itself belongs to the Enterprise
200
+ * license client (ee/Server/License): this is a no-op on the Community
201
+ * Edition, on oneuptime.com (billing enabled) and whenever the license is
202
+ * not valid or in grace, and it never counts the User table on a license
203
+ * with no seat limit.
201
204
  */
202
- await EnterpriseLicenseSeatUtil.assertSeatAvailableForNewUser({
203
- getLocalUserCount: async (): Promise<number> => {
204
- const userCount: PositiveNumber = await this.countBy({
205
- query: {},
206
- props: {
207
- isRoot: true,
208
- },
209
- });
210
-
211
- return userCount.toNumber();
212
- },
213
- });
205
+ await EnterpriseEdition.assertSeatAvailableForNewUser();
214
206
 
215
207
  /*
216
208
  * clickIds / firstTouchAttribution are publicly creatable jsonb columns
@@ -1310,7 +1302,8 @@ export class Service extends DatabaseService<Model> {
1310
1302
  );
1311
1303
  }
1312
1304
 
1313
- const token: string = ObjectID.generate().toString();
1305
+ // A bearer secret: from the CSPRNG, never ObjectID's non-crypto fallback.
1306
+ const token: string = crypto.randomUUID();
1314
1307
 
1315
1308
  const hashedToken: string = await HashedString.hashValue(
1316
1309
  token,
@@ -1,5 +1,6 @@
1
1
  import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType";
2
2
  import ColumnPermissions from "./ColumnPermission";
3
+ import EditionPermissions from "./EditionPermission";
3
4
  import TablePermission from "./TablePermission";
4
5
  import TenantPermission from "./TenantPermission";
5
6
  import BaseModel from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
@@ -15,6 +16,20 @@ export default class CreatePermission {
15
16
  data: TBaseModel,
16
17
  props: DatabaseCommonInteractionProps,
17
18
  ): void {
19
+ /*
20
+ * Master admins skip every table-level check below, but not the edition
21
+ * check: creating enterprise configuration (global SSO/OIDC providers
22
+ * are only ever created by master admins) needs the license for them
23
+ * too. Everyone else gets the same check through TablePermission.
24
+ */
25
+ if (props.isMasterAdmin && !props.isRoot) {
26
+ EditionPermissions.checkEditionPermissions(
27
+ modelType,
28
+ props,
29
+ DatabaseRequestType.Create,
30
+ );
31
+ }
32
+
18
33
  // If system is making this query then let the query run!
19
34
  if (props.isRoot || props.isMasterAdmin) {
20
35
  return;
@@ -1,46 +1,236 @@
1
- import {
2
- IsBillingEnabled,
3
- IsEnterpriseEdition,
4
- } from "../../../../Server/EnvironmentConfig";
1
+ import { IsBillingEnabled } from "../../../../Server/EnvironmentConfig";
2
+ import EnterpriseEdition from "../../../Enterprise/EnterpriseEdition";
3
+ import EnterpriseFeature, {
4
+ ALL_ENTERPRISE_FEATURES,
5
+ } from "../../../Enterprise/EnterpriseFeature";
6
+ import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType";
5
7
  import BaseModel, {
6
8
  DatabaseBaseModelType,
7
9
  } from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
8
10
  import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
9
- import PaymentRequiredException from "../../../../Types/Exception/PaymentRequiredException";
10
11
  import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
11
12
 
13
+ // Whether one column value of an update can only tighten security.
14
+ export type TightenOnlyColumnRule = (value: unknown) => boolean;
15
+
16
+ /*
17
+ * Switching an identity provider off (isEnabled: false). Switching one on is
18
+ * configuration and needs the license; only the literal false counts, so a
19
+ * value the database might coerce ("false", 0) is judged by the full check.
20
+ */
21
+ const isSwitchingOff: TightenOnlyColumnRule = (value: unknown): boolean => {
22
+ return value === false;
23
+ };
24
+
25
+ /*
26
+ * The shortest SCIM bearer token a rotation may set without the license. The
27
+ * Dashboard's "Reset token" generates a UUID (36 characters) and services
28
+ * default to one, so this only turns away a token short enough to be weaker
29
+ * than the one it replaces.
30
+ */
31
+ export const MIN_ROTATED_SCIM_BEARER_TOKEN_LENGTH: number = 32;
32
+
33
+ // Replacing a (possibly leaked) SCIM bearer token with a new, long one.
34
+ const isRotatedBearerToken: TightenOnlyColumnRule = (
35
+ value: unknown,
36
+ ): boolean => {
37
+ return (
38
+ typeof value === "string" &&
39
+ value.trim().length >= MIN_ROTATED_SCIM_BEARER_TOKEN_LENGTH
40
+ );
41
+ };
42
+
43
+ /*
44
+ * The updates an administrator may make to enterprise configuration while
45
+ * the feature is unavailable, because they can only tighten security: the
46
+ * incident-response moves. Keyed by table name, like the facade's feature
47
+ * map; a model that is not listed has none.
48
+ *
49
+ * isEnabled: false disable an SSO/OIDC provider (project, status page or
50
+ * global), or a global provider's attachment to a
51
+ * project.
52
+ * bearerToken rotate a leaked SCIM bearer token.
53
+ *
54
+ * TeamComplianceSetting has no entry: switching a compliance rule off
55
+ * relaxes it.
56
+ */
57
+ const TIGHTEN_ONLY_UPDATES: ReadonlyMap<
58
+ string,
59
+ Readonly<Record<string, TightenOnlyColumnRule>>
60
+ > = new Map<string, Readonly<Record<string, TightenOnlyColumnRule>>>([
61
+ ["GlobalSSO", { isEnabled: isSwitchingOff }],
62
+ ["GlobalOIDC", { isEnabled: isSwitchingOff }],
63
+ ["GlobalSSOProject", { isEnabled: isSwitchingOff }],
64
+ ["GlobalOIDCProject", { isEnabled: isSwitchingOff }],
65
+ ["ProjectSSO", { isEnabled: isSwitchingOff }],
66
+ ["ProjectOIDC", { isEnabled: isSwitchingOff }],
67
+ ["StatusPageSSO", { isEnabled: isSwitchingOff }],
68
+ ["StatusPageOIDC", { isEnabled: isSwitchingOff }],
69
+ ["ProjectSCIM", { bearerToken: isRotatedBearerToken }],
70
+ ["StatusPageSCIM", { bearerToken: isRotatedBearerToken }],
71
+ ]);
72
+
12
73
  export default class EditionPermissions {
13
74
  /*
14
- * Gates models marked with @TableEditionAccessControl({ requiresEnterprise: true }).
75
+ * Gates the enterprise CONFIGURATION models, the ones marked with
76
+ * @TableEditionAccessControl({ requiresEnterprise: true }): project, global
77
+ * and status page SSO/OIDC providers, SCIM configurations and team
78
+ * compliance settings.
15
79
  *
16
- * On self-hosted builds (billing disabled) the model is only accessible when
17
- * the binary was built as the Enterprise Edition (IS_ENTERPRISE_EDITION=true).
80
+ * Only creating and updating them needs the license. Reading and deleting
81
+ * are always allowed, so an install that dropped to the Community Edition,
82
+ * or whose license lapsed, can still see what it has configured and remove
83
+ * it. Only configuration is gated here, and none of it is ever deleted.
84
+ * Whether what exists RUNS is decided at runtime by
85
+ * EnterpriseEdition.isFeatureActive: SSO, SCIM and audit logging stop while
86
+ * a self-hosted license is lapsed and resume when a license is activated.
18
87
  *
19
- * On the cloud / billing-enabled deployment we leave enforcement to
20
- * BillingPermission — it already gates these models via the model's
21
- * @TableBillingAccessControl plan tier, so a second check would be redundant.
88
+ * The one kind of update that needs no license either is a TIGHTEN-ONLY
89
+ * update (see TIGHTEN_ONLY_UPDATES): every column it writes is on the
90
+ * model's list with a value that can only tighten security - disabling an
91
+ * identity provider, rotating a leaked SCIM bearer token. A lapsed license
92
+ * must never stand between an administrator and those moves during an
93
+ * incident. The same holds on the Community Edition, deliberately and for
94
+ * the same reason reads and deletes do: the move can only reduce what the
95
+ * configuration allows, and a provider disabled (or a token rotated) there
96
+ * stays so when the install returns to the Enterprise Edition, instead of
97
+ * coming back live. Anything else in the same update - even one more
98
+ * column - makes it an ordinary update, which needs the license.
99
+ *
100
+ * `updateData` is what the update writes. Only UpdatePermission, which has
101
+ * it, passes it; without it an update is judged by the full check (fail
102
+ * closed). TablePermission leaves updates to UpdatePermission for that
103
+ * reason.
104
+ *
105
+ * Master admins are subject to this check - Create/UpdatePermission call it
106
+ * before their master-admin early return - because the global SSO/OIDC
107
+ * models are only ever written by master admins and would otherwise never
108
+ * be gated. Internal root writes (props.isRoot) are never checked.
109
+ *
110
+ * On the cloud / billing-enabled deployment enforcement is left to
111
+ * BillingPermission: it already gates these models by the plan tier in
112
+ * their @TableBillingAccessControl, so a second check would be redundant.
113
+ *
114
+ * Deliberately synchronous. TablePermission and CreatePermission are
115
+ * synchronous and their callers do not await them, so an async check with a
116
+ * missed await would turn the refusal into an unhandled rejection and let
117
+ * the write through. The license is read from the enterprise module's
118
+ * cached snapshot, and an unknown snapshot refuses the write (fail closed).
22
119
  */
23
120
  @CaptureSpan()
24
121
  public static checkEditionPermissions(
25
122
  modelType: DatabaseBaseModelType,
26
- _props: DatabaseCommonInteractionProps,
123
+ props: DatabaseCommonInteractionProps,
124
+ operation: DatabaseRequestType,
125
+ updateData?: unknown,
27
126
  ): void {
28
127
  if (IsBillingEnabled) {
29
128
  return;
30
129
  }
31
130
 
131
+ if (props.isRoot) {
132
+ return;
133
+ }
134
+
135
+ if (
136
+ operation !== DatabaseRequestType.Create &&
137
+ operation !== DatabaseRequestType.Update
138
+ ) {
139
+ return;
140
+ }
141
+
32
142
  const model: BaseModel = new modelType();
33
143
 
34
144
  if (!model.requiresEnterprise) {
35
145
  return;
36
146
  }
37
147
 
38
- if (!IsEnterpriseEdition) {
39
- throw new PaymentRequiredException(
40
- `${model.singularName || "This feature"} is only available on the OneUptime Enterprise Edition. ` +
41
- `Please switch to the Enterprise Edition build to enable this feature. ` +
42
- `See https://oneuptime.com/enterprise/overview for details.`,
43
- );
148
+ if (
149
+ operation === DatabaseRequestType.Update &&
150
+ EditionPermissions.isTightenOnlyUpdate(model.tableName, updateData)
151
+ ) {
152
+ return;
153
+ }
154
+
155
+ const feature: EnterpriseFeature | null =
156
+ EnterpriseEdition.getModelFeature(modelType);
157
+
158
+ if (feature) {
159
+ EnterpriseEdition.assertFeatureAvailableSync(feature);
160
+ return;
161
+ }
162
+
163
+ /*
164
+ * An enterprise model the facade has no feature for (its guard test
165
+ * should make this impossible). Fail closed: only a license entitled to
166
+ * every enterprise feature may configure it.
167
+ */
168
+ for (const enterpriseFeature of ALL_ENTERPRISE_FEATURES) {
169
+ EnterpriseEdition.assertFeatureAvailableSync(enterpriseFeature);
170
+ }
171
+ }
172
+
173
+ /*
174
+ * True when the update writes at least one column, and every column it
175
+ * writes is on the table's tighten-only list with a value that passes the
176
+ * column's rule. Columns set to undefined are not written and are ignored.
177
+ * Anything that is not a plain object of columns is not tighten-only.
178
+ */
179
+ public static isTightenOnlyUpdate(
180
+ tableName: string | null | undefined,
181
+ updateData: unknown,
182
+ ): boolean {
183
+ const rules: Readonly<Record<string, TightenOnlyColumnRule>> | undefined =
184
+ tableName ? TIGHTEN_ONLY_UPDATES.get(tableName) : undefined;
185
+
186
+ if (!rules) {
187
+ return false;
188
+ }
189
+
190
+ if (
191
+ !updateData ||
192
+ typeof updateData !== "object" ||
193
+ Array.isArray(updateData)
194
+ ) {
195
+ return false;
44
196
  }
197
+
198
+ const writtenColumns: Array<[string, unknown]> = Object.entries(
199
+ updateData as Record<string, unknown>,
200
+ ).filter(([, value]: [string, unknown]): boolean => {
201
+ return value !== undefined;
202
+ });
203
+
204
+ if (writtenColumns.length === 0) {
205
+ return false;
206
+ }
207
+
208
+ return writtenColumns.every(
209
+ ([column, value]: [string, unknown]): boolean => {
210
+ const rule: TightenOnlyColumnRule | undefined =
211
+ Object.prototype.hasOwnProperty.call(rules, column)
212
+ ? rules[column]
213
+ : undefined;
214
+
215
+ return Boolean(rule && rule(value));
216
+ },
217
+ );
218
+ }
219
+
220
+ // The tables with tighten-only updates, and their columns (for guard tests).
221
+ public static getTightenOnlyColumns(): ReadonlyMap<
222
+ string,
223
+ ReadonlyArray<string>
224
+ > {
225
+ const columns: Map<string, ReadonlyArray<string>> = new Map<
226
+ string,
227
+ ReadonlyArray<string>
228
+ >();
229
+
230
+ for (const [tableName, rules] of TIGHTEN_ONLY_UPDATES) {
231
+ columns.set(tableName, Object.keys(rules));
232
+ }
233
+
234
+ return columns;
45
235
  }
46
236
  }
@@ -57,10 +57,17 @@ export default class TablePermission {
57
57
  BillingPermissions.checkBillingPermissions(modelType, props, type);
58
58
 
59
59
  /*
60
- * 3rd CHECK: Is this an enterprise-only feature being accessed on the
61
- * community self-hosted build?
60
+ * 3rd CHECK: Is this a create of enterprise configuration that the
61
+ * edition or the license does not allow? (Reads and deletes always pass.)
62
+ * Updates are checked by UpdatePermission.checkUpdatePermissions instead,
63
+ * which every update runs and which sees what the update writes: an
64
+ * update that only tightens security (disabling an identity provider,
65
+ * rotating a SCIM token) needs no license, and this check, which is not
66
+ * handed the data, could only refuse it.
62
67
  */
63
- EditionPermissions.checkEditionPermissions(modelType, props);
68
+ if (type !== DatabaseRequestType.Update) {
69
+ EditionPermissions.checkEditionPermissions(modelType, props, type);
70
+ }
64
71
 
65
72
  // 4th CHECK: Does user have access to CRUD data on this model.
66
73
  const userPermissions: Array<UserPermission> =
@@ -3,6 +3,7 @@ import Query from "../Query";
3
3
  import AccessControlUtil from "./AccessControlPermission";
4
4
  import BasePermission, { CheckPermissionBaseInterface } from "./BasePermission";
5
5
  import ColumnPermissions from "./ColumnPermission";
6
+ import EditionPermissions from "./EditionPermission";
6
7
  import TablePermission from "./TablePermission";
7
8
  import BaseModel from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
8
9
  import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
@@ -35,6 +36,27 @@ export default class UpdatePermission {
35
36
  data: QueryDeepPartialEntity<TBaseModel>,
36
37
  props: DatabaseCommonInteractionProps,
37
38
  ): Promise<Query<TBaseModel>> {
39
+ /*
40
+ * The edition check for updates runs here, the one permission entry
41
+ * point that sees what the update writes: an update that only tightens
42
+ * security (disabling an identity provider, rotating a SCIM token) needs
43
+ * no license, anything else does (see EditionPermission). TablePermission
44
+ * leaves updates to this method for that reason.
45
+ *
46
+ * Master admins skip every table-level check below, but not the edition
47
+ * check: changing enterprise configuration (global SSO/OIDC providers are
48
+ * only ever edited by master admins) needs the license for them too.
49
+ * Everyone else gets it right after the table-level check.
50
+ */
51
+ if (props.isMasterAdmin && !props.isRoot) {
52
+ EditionPermissions.checkEditionPermissions(
53
+ modelType,
54
+ props,
55
+ DatabaseRequestType.Update,
56
+ data,
57
+ );
58
+ }
59
+
38
60
  if (props.isRoot || props.isMasterAdmin) {
39
61
  // If system is making this query then let the query run!
40
62
  return query;
@@ -46,6 +68,13 @@ export default class UpdatePermission {
46
68
  DatabaseRequestType.Update,
47
69
  );
48
70
 
71
+ EditionPermissions.checkEditionPermissions(
72
+ modelType,
73
+ props,
74
+ DatabaseRequestType.Update,
75
+ data,
76
+ );
77
+
49
78
  const checkBasePermission: CheckPermissionBaseInterface<TBaseModel> =
50
79
  await BasePermission.checkPermissions(
51
80
  modelType,