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