@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
@@ -0,0 +1,769 @@
1
+ import EditionEnforcement from "../../../Server/Utils/EditionEnforcement";
2
+ import EnterpriseEdition from "../../../Server/Enterprise/EnterpriseEdition";
3
+ import ProjectService, {
4
+ PROJECT_SSO_REQUIREMENT_COLUMNS,
5
+ } from "../../../Server/Services/ProjectService";
6
+ import StatusPageService, {
7
+ STATUS_PAGE_SSO_REQUIREMENT_COLUMNS,
8
+ } from "../../../Server/Services/StatusPageService";
9
+ import ModelPermission from "../../../Server/Types/Database/Permissions/Index";
10
+ import UpdateBy from "../../../Server/Types/Database/UpdateBy";
11
+ import logger from "../../../Server/Utils/Logger";
12
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
13
+ import PaymentRequiredException from "../../../Types/Exception/PaymentRequiredException";
14
+ import ObjectID from "../../../Types/ObjectID";
15
+ import { UserGlobalAccessPermission } from "../../../Types/Permission";
16
+ import Project from "../../../Models/DatabaseModels/Project";
17
+ import StatusPage from "../../../Models/DatabaseModels/StatusPage";
18
+ import FakeEnterpriseModule, {
19
+ createEditionStateCases,
20
+ createLicenseSnapshotWithStatus,
21
+ EditionStateCase,
22
+ installFakeEnterpriseModule,
23
+ uninstallEnterpriseModule,
24
+ } from "../Enterprise/FakeEnterpriseModule";
25
+ import { setTestBillingEnabled } from "../Enterprise/TestBillingFlag";
26
+ import { getJestSpyOn } from "../../Spy";
27
+ import {
28
+ afterEach,
29
+ beforeEach,
30
+ describe,
31
+ expect,
32
+ jest,
33
+ test,
34
+ } from "@jest/globals";
35
+
36
+ jest.mock("../../../Server/EnvironmentConfig", () => {
37
+ const billingFlag: typeof import("../Enterprise/TestBillingFlag") =
38
+ jest.requireActual(
39
+ "../Enterprise/TestBillingFlag",
40
+ ) as typeof import("../Enterprise/TestBillingFlag");
41
+
42
+ return billingFlag.withLiveBillingFlag(
43
+ jest.requireActual("../../../Server/EnvironmentConfig") as Record<
44
+ string,
45
+ unknown
46
+ >,
47
+ );
48
+ });
49
+
50
+ /*
51
+ * The write half of the SSO requirement read mask
52
+ * (EditionEnforcement.guardSsoRequirementWrite, run by ProjectService and
53
+ * StatusPageService onBeforeUpdate).
54
+ *
55
+ * While SSO is not active - the Community Edition, or an Enterprise install
56
+ * whose license lapsed - reads made for a caller report "Require SSO for
57
+ * login" as off and no required provider (SsoRequirementReadMasking.test.ts).
58
+ * The ee SSO settings card (and any API client doing a read-modify-write)
59
+ * then sends that masked value back when it saves. Before this guard, saving
60
+ * wrote requireSsoForLogin=false over a stored true, so when the license was
61
+ * renewed SSO was silently no longer required.
62
+ *
63
+ * Now, while the caller's reads are masked:
64
+ * - a write of the masked value is dropped and the stored value is kept;
65
+ * - a write that would set a requirement is refused (402);
66
+ * - a write left with nothing to save is refused with the same message.
67
+ * Root writes and every write while SSO is active are untouched.
68
+ *
69
+ * Billing and the edition are pinned in every test. Runs without ee/.
70
+ */
71
+
72
+ const PROJECT_ID: ObjectID = new ObjectID(
73
+ "11111111-1111-4111-8111-111111111111",
74
+ );
75
+ const STATUS_PAGE_ID: ObjectID = new ObjectID(
76
+ "77777777-7777-4777-8777-777777777777",
77
+ );
78
+ const PROVIDER_ID: ObjectID = new ObjectID(
79
+ "88888888-8888-4888-8888-888888888888",
80
+ );
81
+ const OTHER_PROVIDER_ID: ObjectID = new ObjectID(
82
+ "99999999-9999-4999-8999-999999999999",
83
+ );
84
+ const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
85
+
86
+ const ownerProps: () => DatabaseCommonInteractionProps =
87
+ (): DatabaseCommonInteractionProps => {
88
+ return {
89
+ userId: USER_ID,
90
+ tenantId: PROJECT_ID,
91
+ userGlobalAccessPermission: {
92
+ projectIds: [PROJECT_ID],
93
+ } as unknown as UserGlobalAccessPermission,
94
+ };
95
+ };
96
+
97
+ const masterAdminProps: () => DatabaseCommonInteractionProps =
98
+ (): DatabaseCommonInteractionProps => {
99
+ return { userId: USER_ID, isMasterAdmin: true };
100
+ };
101
+
102
+ const installLapsedLicense: () => FakeEnterpriseModule =
103
+ (): FakeEnterpriseModule => {
104
+ return installFakeEnterpriseModule({
105
+ snapshot: createLicenseSnapshotWithStatus("expired"),
106
+ });
107
+ };
108
+
109
+ const guard: (
110
+ data: Record<string, unknown>,
111
+ props?: DatabaseCommonInteractionProps | null,
112
+ columns?: ReadonlyArray<string>,
113
+ ) => void = (
114
+ data: Record<string, unknown>,
115
+ props?: DatabaseCommonInteractionProps | null,
116
+ columns?: ReadonlyArray<string>,
117
+ ): void => {
118
+ EditionEnforcement.guardSsoRequirementWrite({
119
+ props: props === undefined ? ownerProps() : props,
120
+ data,
121
+ columns: columns || PROJECT_SSO_REQUIREMENT_COLUMNS,
122
+ });
123
+ };
124
+
125
+ const catchError: (run: () => unknown) => unknown = (
126
+ run: () => unknown,
127
+ ): unknown => {
128
+ try {
129
+ run();
130
+ } catch (err) {
131
+ return err;
132
+ }
133
+
134
+ return null;
135
+ };
136
+
137
+ beforeEach(() => {
138
+ setTestBillingEnabled(false);
139
+ uninstallEnterpriseModule();
140
+ getJestSpyOn(logger, "warn").mockImplementation((): void => {
141
+ return undefined;
142
+ });
143
+ getJestSpyOn(logger, "info").mockImplementation((): void => {
144
+ return undefined;
145
+ });
146
+ });
147
+
148
+ afterEach(() => {
149
+ uninstallEnterpriseModule();
150
+ setTestBillingEnabled(false);
151
+ jest.restoreAllMocks();
152
+ });
153
+
154
+ describe("EditionEnforcement.guardSsoRequirementWrite", () => {
155
+ test("the columns it guards are the ones the reads mask", () => {
156
+ expect([...PROJECT_SSO_REQUIREMENT_COLUMNS].sort()).toEqual([
157
+ "requireSsoForLogin",
158
+ "requireSsoWithSsoProviderId",
159
+ ]);
160
+ expect([...STATUS_PAGE_SSO_REQUIREMENT_COLUMNS]).toEqual([
161
+ "requireSsoForLogin",
162
+ ]);
163
+ });
164
+
165
+ describe("in every deployment state, it acts exactly when reads are masked", () => {
166
+ const states: Array<EditionStateCase> = createEditionStateCases();
167
+
168
+ test.each(
169
+ states.map((state: EditionStateCase): [string, EditionStateCase] => {
170
+ return [state.label, state];
171
+ }),
172
+ )("%s", (_label: string, state: EditionStateCase) => {
173
+ state.apply();
174
+
175
+ const isMasked: boolean =
176
+ EditionEnforcement.shouldMaskSsoRequirementOnRead(ownerProps());
177
+
178
+ // SSO runs exactly where the state says it does, and only then is nothing masked.
179
+ expect(isMasked).toBe(!state.isActive);
180
+
181
+ const data: Record<string, unknown> = {
182
+ requireSsoForLogin: false,
183
+ requireSsoWithSsoProviderId: null,
184
+ name: "Renamed",
185
+ };
186
+
187
+ guard(data);
188
+
189
+ if (isMasked) {
190
+ expect(data).toEqual({ name: "Renamed" });
191
+ } else {
192
+ expect(data).toEqual({
193
+ requireSsoForLogin: false,
194
+ requireSsoWithSsoProviderId: null,
195
+ name: "Renamed",
196
+ });
197
+ }
198
+
199
+ const turningOn: Record<string, unknown> = { requireSsoForLogin: true };
200
+ const error: unknown = catchError((): void => {
201
+ guard(turningOn);
202
+ });
203
+
204
+ if (isMasked) {
205
+ expect(error).toBeInstanceOf(PaymentRequiredException);
206
+ expect(turningOn).toEqual({ requireSsoForLogin: true });
207
+ } else {
208
+ expect(error).toBeNull();
209
+ expect(turningOn).toEqual({ requireSsoForLogin: true });
210
+ }
211
+ });
212
+ });
213
+
214
+ describe("while SSO is not active (license lapsed)", () => {
215
+ beforeEach(() => {
216
+ installLapsedLicense();
217
+ });
218
+
219
+ test("drops a write of the masked value and keeps the rest of the write", () => {
220
+ const data: Record<string, unknown> = {
221
+ requireSsoForLogin: false,
222
+ requireSsoWithSsoProviderId: null,
223
+ name: "Renamed",
224
+ };
225
+
226
+ guard(data);
227
+
228
+ expect(data).toEqual({ name: "Renamed" });
229
+ });
230
+
231
+ test("does the same for a master admin, whose reads are masked too", () => {
232
+ const data: Record<string, unknown> = {
233
+ requireSsoForLogin: false,
234
+ name: "Renamed",
235
+ };
236
+
237
+ guard(data, masterAdminProps());
238
+
239
+ expect(data).toEqual({ name: "Renamed" });
240
+ });
241
+
242
+ test("refuses the settings form's save, which carries nothing but the masked values", () => {
243
+ const data: Record<string, unknown> = {
244
+ requireSsoForLogin: false,
245
+ requireSsoWithSsoProviderId: null,
246
+ };
247
+
248
+ const error: unknown = catchError((): void => {
249
+ guard(data);
250
+ });
251
+
252
+ expect(error).toBeInstanceOf(PaymentRequiredException);
253
+ expect((error as Error).message).toBe(
254
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
255
+ );
256
+ });
257
+
258
+ test.each([
259
+ ["switching the requirement on", { requireSsoForLogin: true }],
260
+ [
261
+ "choosing a required provider",
262
+ { requireSsoWithSsoProviderId: OTHER_PROVIDER_ID },
263
+ ],
264
+ [
265
+ "choosing a provider next to the masked flag",
266
+ {
267
+ requireSsoForLogin: false,
268
+ requireSsoWithSsoProviderId: PROVIDER_ID,
269
+ name: "Renamed",
270
+ },
271
+ ],
272
+ ["an odd value a database reads as true", { requireSsoForLogin: "true" }],
273
+ ["a number", { requireSsoForLogin: 1 }],
274
+ ] as Array<[string, Record<string, unknown>]>)(
275
+ "refuses %s, and leaves the write untouched",
276
+ (_label: string, write: Record<string, unknown>) => {
277
+ const data: Record<string, unknown> = { ...write };
278
+
279
+ const error: unknown = catchError((): void => {
280
+ guard(data);
281
+ });
282
+
283
+ expect(error).toBeInstanceOf(PaymentRequiredException);
284
+ expect(data).toEqual(write);
285
+ },
286
+ );
287
+
288
+ test("leaves a write that does not touch the requirement alone", () => {
289
+ const data: Record<string, unknown> = { name: "Renamed" };
290
+
291
+ guard(data);
292
+
293
+ expect(data).toEqual({ name: "Renamed" });
294
+ });
295
+
296
+ test("counts a key left undefined as nothing to save", () => {
297
+ expect(
298
+ catchError((): void => {
299
+ guard({ requireSsoForLogin: false, name: undefined });
300
+ }),
301
+ ).toBeInstanceOf(PaymentRequiredException);
302
+ });
303
+
304
+ test("never touches an internal (root) write", () => {
305
+ const switchingOff: Record<string, unknown> = {
306
+ requireSsoForLogin: false,
307
+ };
308
+ const switchingOn: Record<string, unknown> = { requireSsoForLogin: true };
309
+
310
+ guard(switchingOff, { isRoot: true });
311
+ guard(switchingOn, { isRoot: true });
312
+ guard(switchingOff, null);
313
+
314
+ expect(switchingOff).toEqual({ requireSsoForLogin: false });
315
+ expect(switchingOn).toEqual({ requireSsoForLogin: true });
316
+ });
317
+
318
+ test("a renewal lifts it without a restart", () => {
319
+ const fake: FakeEnterpriseModule = installLapsedLicense();
320
+
321
+ const lapsedWrite: Record<string, unknown> = {
322
+ requireSsoForLogin: false,
323
+ name: "Renamed",
324
+ };
325
+ guard(lapsedWrite);
326
+ expect(lapsedWrite).toEqual({ name: "Renamed" });
327
+
328
+ fake.setSnapshot(createLicenseSnapshotWithStatus("valid"));
329
+
330
+ const renewedWrite: Record<string, unknown> = {
331
+ requireSsoForLogin: false,
332
+ name: "Renamed",
333
+ };
334
+ guard(renewedWrite);
335
+ expect(renewedWrite).toEqual({
336
+ requireSsoForLogin: false,
337
+ name: "Renamed",
338
+ });
339
+ });
340
+ });
341
+
342
+ describe("the refusal explains itself", () => {
343
+ test("on a lapsed Enterprise install it names the license and where to renew it", () => {
344
+ installLapsedLicense();
345
+
346
+ const error: unknown = catchError((): void => {
347
+ guard({ requireSsoForLogin: true });
348
+ });
349
+
350
+ expect((error as Error).message).toBe(
351
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
352
+ );
353
+ expect(
354
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
355
+ ).toContain("edition label in the Admin Dashboard header");
356
+ expect(
357
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
358
+ ).toContain("The saved setting is kept");
359
+ });
360
+
361
+ test("on the Community Edition it names the edition", () => {
362
+ const error: unknown = catchError((): void => {
363
+ guard({ requireSsoForLogin: true });
364
+ });
365
+
366
+ expect(error).toBeInstanceOf(PaymentRequiredException);
367
+ expect((error as Error).message).toBe(
368
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
369
+ );
370
+ expect(
371
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
372
+ ).toContain("Community Edition");
373
+ expect(
374
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
375
+ ).toContain(
376
+ "with a valid license (or during its 14-day trial, or the 30-day grace period after a license expires)",
377
+ );
378
+ });
379
+
380
+ test("neither message claims anything keeps working", () => {
381
+ for (const message of [
382
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
383
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
384
+ ]) {
385
+ expect(message).not.toMatch(/keeps? working|keeps? running/i);
386
+ expect(message).toContain("is not enforced");
387
+ }
388
+ });
389
+
390
+ test("an edition check that throws still refuses, with the license message", () => {
391
+ installLapsedLicense();
392
+ getJestSpyOn(logger, "error").mockImplementation((): void => {
393
+ return undefined;
394
+ });
395
+ getJestSpyOn(EnterpriseEdition, "isLoaded").mockImplementation(
396
+ (): boolean => {
397
+ throw new Error("edition check exploded");
398
+ },
399
+ );
400
+ getJestSpyOn(
401
+ EditionEnforcement,
402
+ "shouldMaskSsoRequirementOnRead",
403
+ ).mockReturnValue(true);
404
+
405
+ const error: unknown = catchError((): void => {
406
+ guard({ requireSsoForLogin: true });
407
+ });
408
+
409
+ expect(error).toBeInstanceOf(PaymentRequiredException);
410
+ expect((error as Error).message).toBe(
411
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
412
+ );
413
+ });
414
+ });
415
+ });
416
+
417
+ /*
418
+ * The services run the guard from onBeforeUpdate. These go through the real
419
+ * DatabaseService update path; only the repository, the permission checks
420
+ * and the side effects after the write (workflow, realtime, audit) are
421
+ * stubbed.
422
+ */
423
+ describe("updates through the services keep a stored requirement while SSO is not active", () => {
424
+ type StoredRow = Record<string, unknown>;
425
+
426
+ let storedProject: StoredRow;
427
+ let storedStatusPage: StoredRow;
428
+ let projectWrites: Array<Record<string, unknown>>;
429
+ let statusPageWrites: Array<Record<string, unknown>>;
430
+
431
+ const stubService: (
432
+ service: typeof ProjectService | typeof StatusPageService,
433
+ id: ObjectID,
434
+ stored: () => StoredRow,
435
+ writes: () => Array<Record<string, unknown>>,
436
+ createItem: () => Project | StatusPage,
437
+ ) => void = (
438
+ service: typeof ProjectService | typeof StatusPageService,
439
+ id: ObjectID,
440
+ stored: () => StoredRow,
441
+ writes: () => Array<Record<string, unknown>>,
442
+ createItem: () => Project | StatusPage,
443
+ ): void => {
444
+ getJestSpyOn(service, "_findBy").mockImplementation(
445
+ async (): Promise<Array<Project | StatusPage>> => {
446
+ const item: Project | StatusPage = createItem();
447
+ item._id = id.toString();
448
+ return [item];
449
+ },
450
+ );
451
+
452
+ getJestSpyOn(service, "getRepository").mockReturnValue({
453
+ update: async (
454
+ _where: unknown,
455
+ set: Record<string, unknown>,
456
+ ): Promise<{ affected: number }> => {
457
+ const written: Record<string, unknown> = { ...set };
458
+ delete written["version"];
459
+ writes().push(written);
460
+ Object.assign(stored(), written);
461
+ return { affected: 1 };
462
+ },
463
+ save: async (): Promise<never> => {
464
+ throw new Error("this update must not go through save()");
465
+ },
466
+ });
467
+
468
+ getJestSpyOn(service, "onTriggerWorkflow").mockResolvedValue(undefined);
469
+ getJestSpyOn(service, "onTriggerRealtime").mockResolvedValue(undefined);
470
+ };
471
+
472
+ const updateProject: (
473
+ data: Record<string, unknown>,
474
+ props?: DatabaseCommonInteractionProps,
475
+ ) => Promise<number> = (
476
+ data: Record<string, unknown>,
477
+ props?: DatabaseCommonInteractionProps,
478
+ ): Promise<number> => {
479
+ return ProjectService.updateOneById({
480
+ id: PROJECT_ID,
481
+ data: data as UpdateBy<Project>["data"],
482
+ props: props || ownerProps(),
483
+ });
484
+ };
485
+
486
+ const updateStatusPage: (
487
+ data: Record<string, unknown>,
488
+ props?: DatabaseCommonInteractionProps,
489
+ ) => Promise<number> = (
490
+ data: Record<string, unknown>,
491
+ props?: DatabaseCommonInteractionProps,
492
+ ): Promise<number> => {
493
+ return StatusPageService.updateOneById({
494
+ id: STATUS_PAGE_ID,
495
+ data: data as UpdateBy<StatusPage>["data"],
496
+ props: props || ownerProps(),
497
+ });
498
+ };
499
+
500
+ beforeEach(() => {
501
+ storedProject = {
502
+ name: "Acme",
503
+ requireSsoForLogin: true,
504
+ requireSsoWithSsoProviderId: PROVIDER_ID,
505
+ };
506
+ storedStatusPage = { name: "Customer Status", requireSsoForLogin: true };
507
+ projectWrites = [];
508
+ statusPageWrites = [];
509
+
510
+ getJestSpyOn(
511
+ ModelPermission,
512
+ "checkUpdatePermissionByModel",
513
+ ).mockResolvedValue(undefined);
514
+ getJestSpyOn(
515
+ ModelPermission,
516
+ "checkUpdateQueryPermissions",
517
+ ).mockImplementation(
518
+ async (_modelType: unknown, query: unknown): Promise<unknown> => {
519
+ return query;
520
+ },
521
+ );
522
+
523
+ const auditLogService: { recordUpdate: () => Promise<void> } =
524
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
525
+ require("../../../Server/Services/AuditLogService").default;
526
+ getJestSpyOn(auditLogService, "recordUpdate").mockResolvedValue(undefined);
527
+
528
+ stubService(
529
+ ProjectService,
530
+ PROJECT_ID,
531
+ (): StoredRow => {
532
+ return storedProject;
533
+ },
534
+ (): Array<Record<string, unknown>> => {
535
+ return projectWrites;
536
+ },
537
+ (): Project => {
538
+ return new Project();
539
+ },
540
+ );
541
+
542
+ stubService(
543
+ StatusPageService,
544
+ STATUS_PAGE_ID,
545
+ (): StoredRow => {
546
+ return storedStatusPage;
547
+ },
548
+ (): Array<Record<string, unknown>> => {
549
+ return statusPageWrites;
550
+ },
551
+ (): StatusPage => {
552
+ return new StatusPage();
553
+ },
554
+ );
555
+ });
556
+
557
+ describe("Project", () => {
558
+ test("license lapsed: saving the masked value with another change keeps the stored requirement", async () => {
559
+ installLapsedLicense();
560
+
561
+ await expect(
562
+ updateProject({
563
+ requireSsoForLogin: false,
564
+ requireSsoWithSsoProviderId: null,
565
+ name: "Renamed",
566
+ }),
567
+ ).resolves.toBe(1);
568
+
569
+ expect(projectWrites).toHaveLength(1);
570
+ expect(projectWrites[0]).not.toHaveProperty("requireSsoForLogin");
571
+ expect(projectWrites[0]).not.toHaveProperty(
572
+ "requireSsoWithSsoProviderId",
573
+ );
574
+ expect(storedProject).toEqual({
575
+ name: "Renamed",
576
+ requireSsoForLogin: true,
577
+ requireSsoWithSsoProviderId: PROVIDER_ID,
578
+ });
579
+ });
580
+
581
+ test("license lapsed: the SSO settings card's save is refused and writes nothing", async () => {
582
+ installLapsedLicense();
583
+
584
+ await expect(
585
+ updateProject({
586
+ requireSsoForLogin: false,
587
+ requireSsoWithSsoProviderId: null,
588
+ }),
589
+ ).rejects.toThrow(
590
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
591
+ );
592
+
593
+ expect(projectWrites).toEqual([]);
594
+ expect(storedProject["requireSsoForLogin"]).toBe(true);
595
+ });
596
+
597
+ test("license lapsed: an update written as a model instance is guarded the same way", async () => {
598
+ installLapsedLicense();
599
+
600
+ const onlyMasked: Project = new Project();
601
+ onlyMasked.requireSsoForLogin = false;
602
+
603
+ await expect(
604
+ ProjectService.updateOneById({
605
+ id: PROJECT_ID,
606
+ data: onlyMasked as unknown as UpdateBy<Project>["data"],
607
+ props: ownerProps(),
608
+ }),
609
+ ).rejects.toBeInstanceOf(PaymentRequiredException);
610
+
611
+ const withName: Project = new Project();
612
+ withName.requireSsoForLogin = false;
613
+ withName.name = "Renamed";
614
+
615
+ await ProjectService.updateOneById({
616
+ id: PROJECT_ID,
617
+ data: withName as unknown as UpdateBy<Project>["data"],
618
+ props: ownerProps(),
619
+ });
620
+
621
+ expect(projectWrites).toHaveLength(1);
622
+ expect(storedProject["name"]).toBe("Renamed");
623
+ expect(storedProject["requireSsoForLogin"]).toBe(true);
624
+ });
625
+
626
+ test("license lapsed: a master admin cannot switch it off either", async () => {
627
+ installLapsedLicense();
628
+
629
+ await updateProject(
630
+ { requireSsoForLogin: false, name: "Renamed" },
631
+ masterAdminProps(),
632
+ );
633
+
634
+ expect(storedProject["requireSsoForLogin"]).toBe(true);
635
+ });
636
+
637
+ test("license lapsed: switching it on is refused", async () => {
638
+ storedProject["requireSsoForLogin"] = false;
639
+ installLapsedLicense();
640
+
641
+ await expect(
642
+ updateProject({ requireSsoForLogin: true }),
643
+ ).rejects.toBeInstanceOf(PaymentRequiredException);
644
+
645
+ expect(projectWrites).toEqual([]);
646
+ expect(storedProject["requireSsoForLogin"]).toBe(false);
647
+ });
648
+
649
+ test("Community Edition: the same save keeps the stored requirement", async () => {
650
+ uninstallEnterpriseModule();
651
+
652
+ await updateProject({ requireSsoForLogin: false, name: "Renamed" });
653
+
654
+ expect(storedProject["requireSsoForLogin"]).toBe(true);
655
+ });
656
+
657
+ test("SSO active: an administrator can still switch the requirement off", async () => {
658
+ installFakeEnterpriseModule({
659
+ snapshot: createLicenseSnapshotWithStatus("valid"),
660
+ });
661
+
662
+ await updateProject({
663
+ requireSsoForLogin: false,
664
+ requireSsoWithSsoProviderId: null,
665
+ });
666
+
667
+ expect(projectWrites).toHaveLength(1);
668
+ expect(storedProject["requireSsoForLogin"]).toBe(false);
669
+ expect(storedProject["requireSsoWithSsoProviderId"]).toBeNull();
670
+ });
671
+
672
+ test("internal root writes are never guarded", async () => {
673
+ installLapsedLicense();
674
+
675
+ await ProjectService.updateOneById({
676
+ id: PROJECT_ID,
677
+ data: { requireSsoForLogin: false } as UpdateBy<Project>["data"],
678
+ props: { isRoot: true },
679
+ });
680
+
681
+ expect(storedProject["requireSsoForLogin"]).toBe(false);
682
+ });
683
+
684
+ test("negative control: without the guard, the masked save switches the stored requirement off", async () => {
685
+ installLapsedLicense();
686
+ getJestSpyOn(
687
+ EditionEnforcement,
688
+ "guardSsoRequirementWrite",
689
+ ).mockImplementation((): void => {
690
+ return undefined;
691
+ });
692
+
693
+ await updateProject({
694
+ requireSsoForLogin: false,
695
+ requireSsoWithSsoProviderId: null,
696
+ name: "Renamed",
697
+ });
698
+
699
+ // This is the bug the guard prevents: renewal would find SSO not required.
700
+ expect(storedProject["requireSsoForLogin"]).toBe(false);
701
+ expect(storedProject["requireSsoWithSsoProviderId"]).toBeNull();
702
+ });
703
+ });
704
+
705
+ describe("StatusPage", () => {
706
+ test("license lapsed: saving the masked value with another change keeps the stored requirement", async () => {
707
+ installLapsedLicense();
708
+
709
+ await expect(
710
+ updateStatusPage({ requireSsoForLogin: false, name: "Renamed" }),
711
+ ).resolves.toBe(1);
712
+
713
+ expect(statusPageWrites).toHaveLength(1);
714
+ expect(statusPageWrites[0]).not.toHaveProperty("requireSsoForLogin");
715
+ expect(storedStatusPage).toEqual({
716
+ name: "Renamed",
717
+ requireSsoForLogin: true,
718
+ });
719
+ });
720
+
721
+ test("license lapsed: the SSO settings card's save is refused and writes nothing", async () => {
722
+ installLapsedLicense();
723
+
724
+ await expect(
725
+ updateStatusPage({ requireSsoForLogin: false }),
726
+ ).rejects.toThrow(
727
+ EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
728
+ );
729
+
730
+ expect(statusPageWrites).toEqual([]);
731
+ expect(storedStatusPage["requireSsoForLogin"]).toBe(true);
732
+ });
733
+
734
+ test("license lapsed: switching it on is refused", async () => {
735
+ storedStatusPage["requireSsoForLogin"] = false;
736
+ installLapsedLicense();
737
+
738
+ await expect(
739
+ updateStatusPage({ requireSsoForLogin: true }),
740
+ ).rejects.toBeInstanceOf(PaymentRequiredException);
741
+
742
+ expect(storedStatusPage["requireSsoForLogin"]).toBe(false);
743
+ });
744
+
745
+ test("SSO active: an administrator can still switch the requirement off", async () => {
746
+ installFakeEnterpriseModule({
747
+ snapshot: createLicenseSnapshotWithStatus("valid"),
748
+ });
749
+
750
+ await updateStatusPage({ requireSsoForLogin: false });
751
+
752
+ expect(storedStatusPage["requireSsoForLogin"]).toBe(false);
753
+ });
754
+
755
+ test("negative control: without the guard, the masked save switches the stored requirement off", async () => {
756
+ installLapsedLicense();
757
+ getJestSpyOn(
758
+ EditionEnforcement,
759
+ "guardSsoRequirementWrite",
760
+ ).mockImplementation((): void => {
761
+ return undefined;
762
+ });
763
+
764
+ await updateStatusPage({ requireSsoForLogin: false, name: "Renamed" });
765
+
766
+ expect(storedStatusPage["requireSsoForLogin"]).toBe(false);
767
+ });
768
+ });
769
+ });