@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.
- package/Models/AnalyticsModels/AuditLog.ts +23 -15
- package/Models/DatabaseModels/GlobalConfig.ts +24 -0
- package/Models/DatabaseModels/Monitor.ts +3 -2
- package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
- 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/Index.ts +2 -0
- package/Server/Middleware/UserAuthorization.ts +38 -16
- package/Server/Services/AuditLogService.ts +84 -1079
- package/Server/Services/EnterpriseLicenseService.ts +38 -0
- package/Server/Services/GlobalConfigService.ts +80 -1
- package/Server/Services/MonitorProbeService.ts +116 -20
- package/Server/Services/MonitorService.ts +56 -4
- 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/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/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 +190 -0
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
- package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -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/IdentityEnterpriseShells.test.tsx +319 -0
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
- 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/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/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/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/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/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
- 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/Permissions/EditionPermission.test.ts +1823 -0
- package/Tests/Server/Types/Markdown.test.ts +181 -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/MonitorCriteriaEvaluator.test.ts +934 -96
- package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -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/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/Graphs/DayUptimeGraph.test.tsx +49 -2
- package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
- package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
- package/Tests/Utils/HostAddressUtil.test.ts +138 -0
- package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
- package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
- package/Types/API/Hostname.ts +63 -16
- 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/MonitorStep.ts +6 -1
- package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
- 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/Graphs/DayUptimeGraph.tsx +123 -15
- 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/Monitor/MonitorDestinationUtil.ts +157 -0
- package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
- 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/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/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/Index.js +2 -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/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/MonitorProbeService.js +90 -17
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +45 -4
- 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/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/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 +96 -0
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
- 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/MonitorCriteriaEvaluator.js +449 -261
- 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/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/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/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/Graphs/DayUptimeGraph.js +90 -17
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.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/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/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,778 +1,109 @@
|
|
|
1
1
|
import AnalyticsDatabaseService from "./AnalyticsDatabaseService";
|
|
2
|
-
import
|
|
3
|
-
import ProjectService from "./ProjectService";
|
|
4
|
-
import { IsBillingEnabled, IsEnterpriseEdition } from "../EnvironmentConfig";
|
|
5
|
-
import QueryHelper from "../Types/Database/QueryHelper";
|
|
6
|
-
import RelationValueUtil from "../Utils/Database/RelationValueUtil";
|
|
2
|
+
import EnterpriseEdition from "../Enterprise/EnterpriseEdition";
|
|
7
3
|
import logger from "../Utils/Logger";
|
|
8
4
|
import AuditLog from "../../Models/AnalyticsModels/AuditLog";
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { getColumnAccessControlForAllColumns } from "../../Types/Database/AccessControl/ColumnAccessControl";
|
|
13
|
-
import TableColumnType from "../../Types/Database/TableColumnType";
|
|
14
|
-
import ObjectID from "../../Types/ObjectID";
|
|
15
|
-
import UserType from "../../Types/UserType";
|
|
16
|
-
import UserService from "./UserService";
|
|
17
|
-
import OneUptimeDate from "../../Types/Date";
|
|
18
|
-
const PROJECT_SETTINGS_CACHE_TTL_MS = 60 * 1000;
|
|
19
|
-
const USER_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
5
|
+
import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
|
|
6
|
+
// Why a create that is not the recorder's is refused.
|
|
7
|
+
export const AUDIT_LOG_CREATE_REFUSED_MESSAGE = "Audit log entries are recorded by OneUptime and cannot be created directly.";
|
|
20
8
|
/*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
9
|
+
* The audit log's ClickHouse table (reads, retention, migrations) and the four
|
|
10
|
+
* entry points every audited write calls.
|
|
11
|
+
*
|
|
12
|
+
* RECORDING is an Enterprise Edition feature and lives in ee/
|
|
13
|
+
* (ee/Server/AuditLog/AuditLogRecorder.ts): which changes are recorded, the
|
|
14
|
+
* diff, redaction, relation names, the actor and the project-settings cache.
|
|
15
|
+
* recordCreate / recordUpdate / recordDelete / invalidateProjectSettings stay
|
|
16
|
+
* here as thin delegates to the recorder the enterprise module registered, so
|
|
17
|
+
* DatabaseService, ProjectService and the user-notification services call
|
|
18
|
+
* exactly what they always called.
|
|
19
|
+
*
|
|
20
|
+
* On the Community Edition there is no recorder: every call is a no-op, and no
|
|
21
|
+
* entry is ever written.
|
|
22
|
+
*
|
|
23
|
+
* The recorder is looked up on EVERY call, never cached at import: the
|
|
24
|
+
* enterprise module registers after core's modules have loaded.
|
|
25
|
+
*
|
|
26
|
+
* An audited write must never fail because of its audit entry, so a delegate
|
|
27
|
+
* never throws - even when a recorder breaks its own promise not to.
|
|
28
|
+
*
|
|
29
|
+
* Entries are written ONLY by the recorder, as root. Any other create is
|
|
30
|
+
* refused (see createMany), so nobody can forge the audit trail.
|
|
23
31
|
*/
|
|
24
|
-
const MAX_RELATION_NAME_LOOKUPS = 100;
|
|
25
|
-
const SKIPPED_FIELDS = new Set([
|
|
26
|
-
"_id",
|
|
27
|
-
"id",
|
|
28
|
-
"createdAt",
|
|
29
|
-
"updatedAt",
|
|
30
|
-
"deletedAt",
|
|
31
|
-
"version",
|
|
32
|
-
"slug",
|
|
33
|
-
]);
|
|
34
|
-
const NAME_CANDIDATE_FIELDS = [
|
|
35
|
-
"name",
|
|
36
|
-
"title",
|
|
37
|
-
"displayName",
|
|
38
|
-
];
|
|
39
32
|
export class AuditLogService extends AnalyticsDatabaseService {
|
|
40
33
|
constructor(clickhouseDatabase) {
|
|
41
34
|
super({ modelType: AuditLog, database: clickhouseDatabase });
|
|
42
|
-
this.projectSettingsCache = new Map();
|
|
43
|
-
this.userCache = new Map();
|
|
44
|
-
this.relatedModelServices = new Map();
|
|
45
35
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const changes = this.buildSnapshotChanges({
|
|
61
|
-
model: data.model,
|
|
62
|
-
item: data.createdItem,
|
|
63
|
-
valueKey: "newValue",
|
|
64
|
-
});
|
|
65
|
-
await this.addRelationNames({
|
|
66
|
-
model: data.model,
|
|
67
|
-
changes,
|
|
68
|
-
projectId,
|
|
69
|
-
});
|
|
70
|
-
await this.insert({
|
|
71
|
-
projectId,
|
|
72
|
-
model: data.model,
|
|
73
|
-
item: data.createdItem,
|
|
74
|
-
resourceId: (_a = data.createdItem.id) !== null && _a !== void 0 ? _a : null,
|
|
75
|
-
action: AuditLogAction.Create,
|
|
76
|
-
changes,
|
|
77
|
-
props: data.props,
|
|
78
|
-
retentionInDays: settings.retentionInDays,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
logger.warn("AuditLog: failed to record create event");
|
|
83
|
-
logger.warn(err);
|
|
36
|
+
/*
|
|
37
|
+
* Refuses every create that is not root. The recorder writes as root
|
|
38
|
+
* (EnterpriseEdition.getAuditLogRecorder()); anyone else - a project owner
|
|
39
|
+
* or admin through POST /audit-log, a master admin, an API key - would be
|
|
40
|
+
* forging entries. The model's create permissions are empty as well, but
|
|
41
|
+
* master admins pass those, so this is the rule that holds for everyone.
|
|
42
|
+
* Checked here rather than in onBeforeCreate: every create goes through
|
|
43
|
+
* this method (create() delegates to it), and hooks can be skipped with
|
|
44
|
+
* props.ignoreHooks.
|
|
45
|
+
*/
|
|
46
|
+
async createMany(createBy) {
|
|
47
|
+
if (!createBy.props.isRoot) {
|
|
48
|
+
throw new NotAuthorizedException(AUDIT_LOG_CREATE_REFUSED_MESSAGE);
|
|
84
49
|
}
|
|
50
|
+
return await super.createMany(createBy);
|
|
85
51
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* nothing the audit trail tracks - an evaluation tick that rewrote only
|
|
91
|
-
* ignored columns, several times an hour per SLO - should not cost a
|
|
92
|
-
* settings lookup.
|
|
93
|
-
*/
|
|
94
|
-
const changes = this.buildUpdateDiff({
|
|
95
|
-
model: data.model,
|
|
96
|
-
before: data.before,
|
|
97
|
-
updatedFields: data.updatedFields,
|
|
98
|
-
});
|
|
99
|
-
if (changes.length === 0) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const projectId = this.resolveProjectId(data.model, data.before, data.props);
|
|
103
|
-
if (!projectId) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const settings = await this.getProjectSettings(projectId);
|
|
107
|
-
if (!this.isEligible(settings, data.props)) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
await this.addRelationNames({
|
|
111
|
-
model: data.model,
|
|
112
|
-
changes,
|
|
113
|
-
projectId,
|
|
114
|
-
});
|
|
115
|
-
await this.insert({
|
|
116
|
-
projectId,
|
|
117
|
-
model: data.model,
|
|
118
|
-
item: data.before,
|
|
119
|
-
resourceId: data.itemId,
|
|
120
|
-
action: AuditLogAction.Update,
|
|
121
|
-
changes,
|
|
122
|
-
props: data.props,
|
|
123
|
-
retentionInDays: settings.retentionInDays,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
logger.warn("AuditLog: failed to record update event");
|
|
128
|
-
logger.warn(err);
|
|
52
|
+
invalidateProjectSettings(projectId) {
|
|
53
|
+
const recorder = this.getRecorder();
|
|
54
|
+
if (!recorder) {
|
|
55
|
+
return;
|
|
129
56
|
}
|
|
130
|
-
}
|
|
131
|
-
async recordDelete(data) {
|
|
132
57
|
try {
|
|
133
|
-
|
|
134
|
-
if (!projectId) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const settings = await this.getProjectSettings(projectId);
|
|
138
|
-
if (!this.isEligible(settings, data.props)) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const changes = this.buildSnapshotChanges({
|
|
142
|
-
model: data.model,
|
|
143
|
-
item: data.deletedItem,
|
|
144
|
-
valueKey: "oldValue",
|
|
145
|
-
});
|
|
146
|
-
await this.addRelationNames({
|
|
147
|
-
model: data.model,
|
|
148
|
-
changes,
|
|
149
|
-
projectId,
|
|
150
|
-
});
|
|
151
|
-
await this.insert({
|
|
152
|
-
projectId,
|
|
153
|
-
model: data.model,
|
|
154
|
-
item: data.deletedItem,
|
|
155
|
-
resourceId: data.itemId,
|
|
156
|
-
action: AuditLogAction.Delete,
|
|
157
|
-
changes,
|
|
158
|
-
props: data.props,
|
|
159
|
-
retentionInDays: settings.retentionInDays,
|
|
160
|
-
});
|
|
58
|
+
recorder.invalidateProjectSettings(projectId);
|
|
161
59
|
}
|
|
162
60
|
catch (err) {
|
|
163
|
-
logger.warn("AuditLog: failed to
|
|
61
|
+
logger.warn("AuditLog: failed to invalidate the cached project audit settings");
|
|
164
62
|
logger.warn(err);
|
|
165
63
|
}
|
|
166
64
|
}
|
|
167
|
-
async
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
item: params.item,
|
|
172
|
-
projectId: params.projectId,
|
|
173
|
-
});
|
|
174
|
-
const rootResource = this.getRootResource({
|
|
175
|
-
model: params.model,
|
|
176
|
-
item: params.item,
|
|
177
|
-
resourceType,
|
|
178
|
-
resourceId: params.resourceId,
|
|
179
|
-
});
|
|
180
|
-
const auditLog = new AuditLog();
|
|
181
|
-
auditLog.projectId = params.projectId;
|
|
182
|
-
auditLog.resourceType = resourceType;
|
|
183
|
-
if (params.resourceId) {
|
|
184
|
-
auditLog.resourceId = params.resourceId;
|
|
185
|
-
}
|
|
186
|
-
if (resourceName) {
|
|
187
|
-
auditLog.resourceName = resourceName;
|
|
188
|
-
}
|
|
189
|
-
if (rootResource.rootResourceType && rootResource.rootResourceId) {
|
|
190
|
-
auditLog.rootResourceType = rootResource.rootResourceType;
|
|
191
|
-
auditLog.rootResourceId = rootResource.rootResourceId;
|
|
192
|
-
}
|
|
193
|
-
auditLog.action = params.action;
|
|
194
|
-
const actor = await this.resolveActor(params.props);
|
|
195
|
-
if (actor.userId) {
|
|
196
|
-
auditLog.userId = actor.userId;
|
|
197
|
-
}
|
|
198
|
-
if (actor.userName) {
|
|
199
|
-
auditLog.userName = actor.userName;
|
|
200
|
-
}
|
|
201
|
-
if (actor.userEmail) {
|
|
202
|
-
auditLog.userEmail = actor.userEmail;
|
|
203
|
-
}
|
|
204
|
-
if (actor.userType) {
|
|
205
|
-
auditLog.userType = actor.userType;
|
|
206
|
-
}
|
|
207
|
-
auditLog.changes = params.changes;
|
|
208
|
-
auditLog.retentionDate = this.computeRetentionDate(params.retentionInDays);
|
|
209
|
-
await this.create({
|
|
210
|
-
data: auditLog,
|
|
211
|
-
props: { isRoot: true },
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
computeRetentionDate(retentionInDays) {
|
|
215
|
-
const days = Math.max(1, Math.min(retentionInDays || 7, 180));
|
|
216
|
-
return OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), days);
|
|
217
|
-
}
|
|
218
|
-
isEligible(settings, props) {
|
|
219
|
-
if (!settings) {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
if (!settings.enableAuditLogs) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
if (!settings.storeSystemEventsInAuditLogs && this.isSystemEvent(props)) {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
if (IsEnterpriseEdition) {
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
if (IsBillingEnabled) {
|
|
232
|
-
return settings.planName === PlanType.Enterprise;
|
|
233
|
-
}
|
|
234
|
-
/*
|
|
235
|
-
* Neither enterprise edition nor billing is enabled — audit logs are not
|
|
236
|
-
* available on the free self-hosted build.
|
|
237
|
-
*/
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
isSystemEvent(props) {
|
|
241
|
-
/*
|
|
242
|
-
* A system event is one not initiated by a user — no userType is set and
|
|
243
|
-
* the operation is running with root privileges (e.g. background jobs,
|
|
244
|
-
* internal cleanup tasks).
|
|
245
|
-
*/
|
|
246
|
-
return !props.userType && Boolean(props.isRoot);
|
|
247
|
-
}
|
|
248
|
-
async getProjectSettings(projectId) {
|
|
249
|
-
var _a;
|
|
250
|
-
const key = projectId.toString();
|
|
251
|
-
const now = Date.now();
|
|
252
|
-
const cached = this.projectSettingsCache.get(key);
|
|
253
|
-
if (cached && cached.expiresAt > now) {
|
|
254
|
-
return cached;
|
|
255
|
-
}
|
|
256
|
-
const project = await ProjectService.findOneById({
|
|
257
|
-
id: projectId,
|
|
258
|
-
select: {
|
|
259
|
-
_id: true,
|
|
260
|
-
enableAuditLogs: true,
|
|
261
|
-
auditLogsRetentionInDays: true,
|
|
262
|
-
storeSystemEventsInAuditLogs: true,
|
|
263
|
-
planName: true,
|
|
264
|
-
},
|
|
265
|
-
props: { isRoot: true },
|
|
266
|
-
});
|
|
267
|
-
if (!project) {
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
const settings = {
|
|
271
|
-
enableAuditLogs: Boolean(project.enableAuditLogs),
|
|
272
|
-
retentionInDays: (_a = project.auditLogsRetentionInDays) !== null && _a !== void 0 ? _a : 7,
|
|
273
|
-
storeSystemEventsInAuditLogs: Boolean(project.storeSystemEventsInAuditLogs),
|
|
274
|
-
planName: project.planName,
|
|
275
|
-
expiresAt: now + PROJECT_SETTINGS_CACHE_TTL_MS,
|
|
276
|
-
};
|
|
277
|
-
this.projectSettingsCache.set(key, settings);
|
|
278
|
-
return settings;
|
|
279
|
-
}
|
|
280
|
-
async resolveActor(props) {
|
|
281
|
-
var _a, _b;
|
|
282
|
-
const userType = props.userType
|
|
283
|
-
? String(props.userType)
|
|
284
|
-
: props.isRoot
|
|
285
|
-
? "System"
|
|
286
|
-
: null;
|
|
287
|
-
if (!props.userId) {
|
|
288
|
-
return {
|
|
289
|
-
userId: null,
|
|
290
|
-
userName: null,
|
|
291
|
-
userEmail: null,
|
|
292
|
-
userType,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
// API-key actions don't have a user record to look up.
|
|
296
|
-
if (props.userType === UserType.API) {
|
|
297
|
-
return {
|
|
298
|
-
userId: props.userId,
|
|
299
|
-
userName: null,
|
|
300
|
-
userEmail: null,
|
|
301
|
-
userType,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
const cached = await this.getUserInfo(props.userId);
|
|
305
|
-
return {
|
|
306
|
-
userId: props.userId,
|
|
307
|
-
userName: (_a = cached === null || cached === void 0 ? void 0 : cached.name) !== null && _a !== void 0 ? _a : null,
|
|
308
|
-
userEmail: (_b = cached === null || cached === void 0 ? void 0 : cached.email) !== null && _b !== void 0 ? _b : null,
|
|
309
|
-
userType,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
async getUserInfo(userId) {
|
|
313
|
-
var _a, _b, _c, _d;
|
|
314
|
-
const key = userId.toString();
|
|
315
|
-
const now = Date.now();
|
|
316
|
-
const cached = this.userCache.get(key);
|
|
317
|
-
if (cached && cached.expiresAt > now) {
|
|
318
|
-
return { name: cached.name, email: cached.email };
|
|
319
|
-
}
|
|
320
|
-
const user = await UserService.findOneById({
|
|
321
|
-
id: userId,
|
|
322
|
-
select: { _id: true, name: true, email: true },
|
|
323
|
-
props: { isRoot: true },
|
|
324
|
-
});
|
|
325
|
-
const name = (_b = (_a = user === null || user === void 0 ? void 0 : user.name) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : null;
|
|
326
|
-
const email = (_d = (_c = user === null || user === void 0 ? void 0 : user.email) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : null;
|
|
327
|
-
this.userCache.set(key, {
|
|
328
|
-
name,
|
|
329
|
-
email,
|
|
330
|
-
expiresAt: now + USER_CACHE_TTL_MS,
|
|
331
|
-
});
|
|
332
|
-
return { name, email };
|
|
333
|
-
}
|
|
334
|
-
resolveProjectId(model, item, props) {
|
|
335
|
-
if (props.tenantId) {
|
|
336
|
-
return props.tenantId;
|
|
337
|
-
}
|
|
338
|
-
const tenantColumn = model.getTenantColumn();
|
|
339
|
-
if (!tenantColumn) {
|
|
340
|
-
return undefined;
|
|
341
|
-
}
|
|
342
|
-
const value = item.getValue(tenantColumn);
|
|
343
|
-
return value !== null && value !== void 0 ? value : undefined;
|
|
344
|
-
}
|
|
345
|
-
/*
|
|
346
|
-
* Where this entry rolls up to (see EnableAuditLogOn.rootResource). A
|
|
347
|
-
* top-level resource points at itself; a child points at the parent id it
|
|
348
|
-
* carries in its configured column.
|
|
349
|
-
*/
|
|
350
|
-
getRootResource(data) {
|
|
351
|
-
var _a;
|
|
352
|
-
const rootResource = (_a = data.model.enableAuditLogOn) === null || _a === void 0 ? void 0 : _a.rootResource;
|
|
353
|
-
if (!rootResource) {
|
|
354
|
-
return {
|
|
355
|
-
rootResourceType: data.resourceType,
|
|
356
|
-
rootResourceId: data.resourceId,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
const rootResourceId = RelationValueUtil.getRelationId(data.item[rootResource.column]);
|
|
360
|
-
if (!rootResourceId) {
|
|
361
|
-
/*
|
|
362
|
-
* A child row that cannot name its parent. Filing it under itself would
|
|
363
|
-
* give it a root type it is not, so it gets no pointer: the entry still
|
|
364
|
-
* shows in the project-wide audit log, just not on a resource's page.
|
|
365
|
-
*/
|
|
366
|
-
return { rootResourceType: null, rootResourceId: null };
|
|
367
|
-
}
|
|
368
|
-
return {
|
|
369
|
-
rootResourceType: rootResource.resourceType,
|
|
370
|
-
rootResourceId: new ObjectID(rootResourceId),
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
/*
|
|
374
|
-
* Everything an entry leaves out: bookkeeping fields, columns nobody may read
|
|
375
|
-
* (read ACL `[]`, so recording them would leak them to audit readers), and
|
|
376
|
-
* the model's ignored columns.
|
|
377
|
-
*/
|
|
378
|
-
getExcludedFields(model) {
|
|
379
|
-
var _a;
|
|
380
|
-
const excluded = new Set(SKIPPED_FIELDS);
|
|
381
|
-
for (const field of this.getRedactedFields(model)) {
|
|
382
|
-
excluded.add(field);
|
|
383
|
-
}
|
|
384
|
-
for (const field of ((_a = model.enableAuditLogOn) === null || _a === void 0 ? void 0 : _a.ignoreColumns) || []) {
|
|
385
|
-
excluded.add(field);
|
|
386
|
-
}
|
|
387
|
-
return excluded;
|
|
388
|
-
}
|
|
389
|
-
getRedactedFields(model) {
|
|
390
|
-
const redacted = new Set();
|
|
391
|
-
const allAccessControl = getColumnAccessControlForAllColumns(model);
|
|
392
|
-
for (const key of Object.keys(allAccessControl)) {
|
|
393
|
-
const ac = allAccessControl[key];
|
|
394
|
-
if (ac && Array.isArray(ac.read) && ac.read.length === 0) {
|
|
395
|
-
redacted.add(key);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return redacted;
|
|
399
|
-
}
|
|
400
|
-
buildSnapshotChanges(data) {
|
|
401
|
-
const changes = [];
|
|
402
|
-
const excludedFields = this.getExcludedFields(data.model);
|
|
403
|
-
const columns = data.model.getTableColumns().columns;
|
|
404
|
-
const itemRecord = data.item;
|
|
405
|
-
for (const column of columns) {
|
|
406
|
-
if (excludedFields.has(column)) {
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
const value = itemRecord[column];
|
|
410
|
-
if (value === undefined) {
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
changes.push({
|
|
414
|
-
field: column,
|
|
415
|
-
[data.valueKey]: this.serializeValue(data.model, column, value),
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
return changes;
|
|
419
|
-
}
|
|
420
|
-
buildUpdateDiff(data) {
|
|
421
|
-
const changes = [];
|
|
422
|
-
const excludedFields = this.getExcludedFields(data.model);
|
|
423
|
-
const beforeRecord = data.before;
|
|
424
|
-
for (const field of Object.keys(data.updatedFields)) {
|
|
425
|
-
if (excludedFields.has(field)) {
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
const newValue = data.updatedFields[field];
|
|
429
|
-
const oldValue = beforeRecord[field];
|
|
430
|
-
if (this.areValuesEqual({
|
|
431
|
-
model: data.model,
|
|
432
|
-
field,
|
|
433
|
-
oldValue,
|
|
434
|
-
newValue,
|
|
435
|
-
})) {
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
changes.push({
|
|
439
|
-
field,
|
|
440
|
-
oldValue: this.serializeValue(data.model, field, oldValue),
|
|
441
|
-
newValue: this.serializeValue(data.model, field, newValue),
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
return changes;
|
|
445
|
-
}
|
|
446
|
-
areValuesEqual(data) {
|
|
447
|
-
const a = data.oldValue;
|
|
448
|
-
const b = data.newValue;
|
|
449
|
-
if (a === b) {
|
|
450
|
-
return true;
|
|
451
|
-
}
|
|
452
|
-
if (a === null || a === undefined) {
|
|
453
|
-
return b === null || b === undefined;
|
|
454
|
-
}
|
|
455
|
-
if (b === null || b === undefined) {
|
|
456
|
-
return false;
|
|
457
|
-
}
|
|
458
|
-
/*
|
|
459
|
-
* A relation is the set of rows it references. Its JSON also carries
|
|
460
|
-
* whatever else happened to be loaded - the before-row's names, the
|
|
461
|
-
* payload's bare ids, the order the join returned - so an unchanged
|
|
462
|
-
* relation would otherwise read as changed.
|
|
463
|
-
*/
|
|
464
|
-
if (this.getRelationMetadata(data.model, data.field)) {
|
|
465
|
-
const sameRelationIds = RelationValueUtil.haveSameRelationIds(a, b);
|
|
466
|
-
if (sameRelationIds !== null) {
|
|
467
|
-
return sameRelationIds;
|
|
468
|
-
}
|
|
65
|
+
async recordCreate(data) {
|
|
66
|
+
const recorder = this.getRecorder();
|
|
67
|
+
if (!recorder) {
|
|
68
|
+
return;
|
|
469
69
|
}
|
|
470
70
|
try {
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
catch (_a) {
|
|
474
|
-
return false;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
// The column's metadata when it is a relation (Entity / EntityArray), else null.
|
|
478
|
-
getRelationMetadata(model, field) {
|
|
479
|
-
if (!model.isTableColumn(field)) {
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
482
|
-
const metadata = model.getTableColumnMetadata(field);
|
|
483
|
-
if (!metadata ||
|
|
484
|
-
!metadata.modelType ||
|
|
485
|
-
(metadata.type !== TableColumnType.Entity &&
|
|
486
|
-
metadata.type !== TableColumnType.EntityArray)) {
|
|
487
|
-
return null;
|
|
488
|
-
}
|
|
489
|
-
return metadata;
|
|
490
|
-
}
|
|
491
|
-
serializeValue(model, field, value) {
|
|
492
|
-
if (value === null || value === undefined) {
|
|
493
|
-
return null;
|
|
494
|
-
}
|
|
495
|
-
/*
|
|
496
|
-
* A related row is recorded as `{ _id, name }`. Serialized as it arrives
|
|
497
|
-
* it would be a model instance's JSON: a bare `{ _id }` from a payload, or
|
|
498
|
-
* whatever columns the read happened to load.
|
|
499
|
-
*/
|
|
500
|
-
if (this.getRelationMetadata(model, field)) {
|
|
501
|
-
if (Array.isArray(value)) {
|
|
502
|
-
return value.map((element) => {
|
|
503
|
-
return this.serializeRelationReference(element);
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
return this.serializeRelationReference(value);
|
|
507
|
-
}
|
|
508
|
-
return this.serializePlainValue(value);
|
|
509
|
-
}
|
|
510
|
-
serializeRelationReference(value) {
|
|
511
|
-
const id = RelationValueUtil.getRelationId(value);
|
|
512
|
-
// Not a reference at all: record it as it is rather than drop it.
|
|
513
|
-
if (!id) {
|
|
514
|
-
return this.serializePlainValue(value);
|
|
71
|
+
await recorder.recordCreate(data);
|
|
515
72
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (name) {
|
|
520
|
-
reference["name"] = name;
|
|
521
|
-
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
logger.warn("AuditLog: failed to record create event");
|
|
75
|
+
logger.warn(err);
|
|
522
76
|
}
|
|
523
|
-
return reference;
|
|
524
77
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (value instanceof ObjectID) {
|
|
530
|
-
return value.toString();
|
|
531
|
-
}
|
|
532
|
-
if (value instanceof Date) {
|
|
533
|
-
return value.toISOString();
|
|
534
|
-
}
|
|
535
|
-
if (typeof value === "string" ||
|
|
536
|
-
typeof value === "number" ||
|
|
537
|
-
typeof value === "boolean") {
|
|
538
|
-
return value;
|
|
78
|
+
async recordUpdate(data) {
|
|
79
|
+
const recorder = this.getRecorder();
|
|
80
|
+
if (!recorder) {
|
|
81
|
+
return;
|
|
539
82
|
}
|
|
540
83
|
try {
|
|
541
|
-
|
|
84
|
+
await recorder.recordUpdate(data);
|
|
542
85
|
}
|
|
543
|
-
catch (
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
/*
|
|
548
|
-
* Relation references are recorded with names so an entry reads
|
|
549
|
-
* "Production -> Staging" rather than as two ids. An update's before-row
|
|
550
|
-
* already carries the old names (DatabaseService selects them), but a
|
|
551
|
-
* payload usually carries ids only - so an unnamed reference first borrows
|
|
552
|
-
* the name of another reference to the same row in the same change, and
|
|
553
|
-
* whatever is still unnamed is looked up. Best effort: a failed lookup leaves
|
|
554
|
-
* the ids, and never costs the entry.
|
|
555
|
-
*/
|
|
556
|
-
async addRelationNames(data) {
|
|
557
|
-
const pendingByModel = new Map();
|
|
558
|
-
for (const change of data.changes) {
|
|
559
|
-
const entry = change;
|
|
560
|
-
const metadata = this.getRelationMetadata(data.model, String(entry["field"]));
|
|
561
|
-
if (!metadata || !metadata.modelType) {
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
const references = [
|
|
565
|
-
...this.getRelationReferences(entry["oldValue"]),
|
|
566
|
-
...this.getRelationReferences(entry["newValue"]),
|
|
567
|
-
];
|
|
568
|
-
const knownNames = new Map();
|
|
569
|
-
for (const reference of references) {
|
|
570
|
-
const name = reference["name"];
|
|
571
|
-
if (typeof name === "string" && name.length > 0) {
|
|
572
|
-
knownNames.set(String(reference["_id"]).toLowerCase(), name);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
for (const reference of references) {
|
|
576
|
-
if (reference["name"]) {
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
const id = String(reference["_id"]).toLowerCase();
|
|
580
|
-
const knownName = knownNames.get(id);
|
|
581
|
-
if (knownName) {
|
|
582
|
-
reference["name"] = knownName;
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
let pending = pendingByModel.get(metadata.modelType);
|
|
586
|
-
if (!pending) {
|
|
587
|
-
pending = {
|
|
588
|
-
modelType: metadata.modelType,
|
|
589
|
-
ids: new Set(),
|
|
590
|
-
references: [],
|
|
591
|
-
};
|
|
592
|
-
pendingByModel.set(metadata.modelType, pending);
|
|
593
|
-
}
|
|
594
|
-
pending.ids.add(id);
|
|
595
|
-
pending.references.push(reference);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
let remainingLookups = MAX_RELATION_NAME_LOOKUPS;
|
|
599
|
-
for (const pending of pendingByModel.values()) {
|
|
600
|
-
if (remainingLookups <= 0) {
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
const ids = Array.from(pending.ids).slice(0, remainingLookups);
|
|
604
|
-
remainingLookups -= ids.length;
|
|
605
|
-
try {
|
|
606
|
-
const names = await this.findRelationNames({
|
|
607
|
-
modelType: pending.modelType,
|
|
608
|
-
ids,
|
|
609
|
-
projectId: data.projectId,
|
|
610
|
-
});
|
|
611
|
-
for (const reference of pending.references) {
|
|
612
|
-
const name = names.get(String(reference["_id"]).toLowerCase());
|
|
613
|
-
if (name) {
|
|
614
|
-
reference["name"] = name;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
catch (err) {
|
|
619
|
-
logger.warn("AuditLog: could not resolve the names of related resources; recording their ids only");
|
|
620
|
-
logger.warn(err);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
getRelationReferences(value) {
|
|
625
|
-
const candidates = Array.isArray(value) ? value : [value];
|
|
626
|
-
return candidates.filter((candidate) => {
|
|
627
|
-
return (candidate !== null &&
|
|
628
|
-
typeof candidate === "object" &&
|
|
629
|
-
!Array.isArray(candidate) &&
|
|
630
|
-
typeof candidate["_id"] === "string");
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
/*
|
|
634
|
-
* Display names for related rows, keyed by lower-cased id.
|
|
635
|
-
*
|
|
636
|
-
* A user is named by their name, else their email, through the same cache
|
|
637
|
-
* the actor lookup uses. Any other model is named by its `name` column, and
|
|
638
|
-
* only rows inside the audited project are read: the ids come from the
|
|
639
|
-
* caller's payload, and naming a row that belongs to another project would
|
|
640
|
-
* copy that project's data into this project's audit trail. A model with no
|
|
641
|
-
* tenant column or no name column resolves nothing.
|
|
642
|
-
*/
|
|
643
|
-
async findRelationNames(data) {
|
|
644
|
-
const names = new Map();
|
|
645
|
-
const ids = Array.from(new Set(data.ids.map((id) => {
|
|
646
|
-
return id.toLowerCase();
|
|
647
|
-
})));
|
|
648
|
-
if (ids.length === 0) {
|
|
649
|
-
return names;
|
|
650
|
-
}
|
|
651
|
-
const relatedModel = new data.modelType();
|
|
652
|
-
if (relatedModel instanceof User) {
|
|
653
|
-
for (const id of ids) {
|
|
654
|
-
const user = await this.getUserInfo(new ObjectID(id));
|
|
655
|
-
const name = (user === null || user === void 0 ? void 0 : user.name) || (user === null || user === void 0 ? void 0 : user.email) || null;
|
|
656
|
-
if (name) {
|
|
657
|
-
names.set(id, name);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
return names;
|
|
661
|
-
}
|
|
662
|
-
const tenantColumn = relatedModel.getTenantColumn();
|
|
663
|
-
if (!tenantColumn || !relatedModel.isTableColumn("name")) {
|
|
664
|
-
return names;
|
|
665
|
-
}
|
|
666
|
-
const rows = await this.getRelatedModelService(data.modelType).findBy({
|
|
667
|
-
query: {
|
|
668
|
-
_id: QueryHelper.any(ids.map((id) => {
|
|
669
|
-
return new ObjectID(id);
|
|
670
|
-
})),
|
|
671
|
-
[tenantColumn]: data.projectId,
|
|
672
|
-
},
|
|
673
|
-
select: { _id: true, name: true },
|
|
674
|
-
skip: 0,
|
|
675
|
-
limit: ids.length,
|
|
676
|
-
props: { isRoot: true, ignoreHooks: true },
|
|
677
|
-
});
|
|
678
|
-
for (const row of rows) {
|
|
679
|
-
const name = this.toDisplayString(row["name"]);
|
|
680
|
-
if (row._id && name) {
|
|
681
|
-
names.set(row._id.toString().toLowerCase(), name);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
return names;
|
|
685
|
-
}
|
|
686
|
-
/*
|
|
687
|
-
* A plain DatabaseService reads the related rows: the lookup needs no
|
|
688
|
-
* service hooks (it runs as root with hooks off), and going through a
|
|
689
|
-
* registry of concrete services would import every service into this one.
|
|
690
|
-
*/
|
|
691
|
-
getRelatedModelService(modelType) {
|
|
692
|
-
let service = this.relatedModelServices.get(modelType);
|
|
693
|
-
if (!service) {
|
|
694
|
-
service = new DatabaseService(modelType);
|
|
695
|
-
this.relatedModelServices.set(modelType, service);
|
|
696
|
-
}
|
|
697
|
-
return service;
|
|
698
|
-
}
|
|
699
|
-
getResourceType(model) {
|
|
700
|
-
if (model.singularName) {
|
|
701
|
-
return model.singularName;
|
|
702
|
-
}
|
|
703
|
-
return model.constructor
|
|
704
|
-
.name;
|
|
705
|
-
}
|
|
706
|
-
getResourceName(item) {
|
|
707
|
-
const itemRecord = item;
|
|
708
|
-
for (const field of NAME_CANDIDATE_FIELDS) {
|
|
709
|
-
if (!item.isTableColumn(field)) {
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
const name = this.toDisplayString(itemRecord[field]);
|
|
713
|
-
if (name) {
|
|
714
|
-
return name;
|
|
715
|
-
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
logger.warn("AuditLog: failed to record update event");
|
|
88
|
+
logger.warn(err);
|
|
716
89
|
}
|
|
717
|
-
return null;
|
|
718
90
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
*/
|
|
724
|
-
async resolveResourceName(data) {
|
|
725
|
-
var _a, _b;
|
|
726
|
-
const ownName = this.getResourceName(data.item);
|
|
727
|
-
if (ownName) {
|
|
728
|
-
return ownName;
|
|
729
|
-
}
|
|
730
|
-
const relation = (_a = data.model.enableAuditLogOn) === null || _a === void 0 ? void 0 : _a.resourceNameRelation;
|
|
731
|
-
if (!relation) {
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
const metadata = this.getRelationMetadata(data.model, relation);
|
|
735
|
-
if (!metadata ||
|
|
736
|
-
!metadata.modelType ||
|
|
737
|
-
metadata.type !== TableColumnType.Entity ||
|
|
738
|
-
!metadata.manyToOneRelationColumn) {
|
|
739
|
-
return null;
|
|
740
|
-
}
|
|
741
|
-
const relatedId = RelationValueUtil.getRelationId(data.item[metadata.manyToOneRelationColumn]);
|
|
742
|
-
if (!relatedId) {
|
|
743
|
-
return null;
|
|
91
|
+
async recordDelete(data) {
|
|
92
|
+
const recorder = this.getRecorder();
|
|
93
|
+
if (!recorder) {
|
|
94
|
+
return;
|
|
744
95
|
}
|
|
745
96
|
try {
|
|
746
|
-
|
|
747
|
-
modelType: metadata.modelType,
|
|
748
|
-
ids: [relatedId],
|
|
749
|
-
projectId: data.projectId,
|
|
750
|
-
});
|
|
751
|
-
return (_b = names.get(relatedId.toLowerCase())) !== null && _b !== void 0 ? _b : null;
|
|
97
|
+
await recorder.recordDelete(data);
|
|
752
98
|
}
|
|
753
99
|
catch (err) {
|
|
754
|
-
logger.warn("AuditLog:
|
|
100
|
+
logger.warn("AuditLog: failed to record delete event");
|
|
755
101
|
logger.warn(err);
|
|
756
|
-
return null;
|
|
757
102
|
}
|
|
758
103
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
* plain object stringifying to "[object Object]" - is not a name.
|
|
763
|
-
*/
|
|
764
|
-
toDisplayString(value) {
|
|
765
|
-
if (typeof value === "string") {
|
|
766
|
-
return value.trim().length > 0 ? value : null;
|
|
767
|
-
}
|
|
768
|
-
if (value instanceof ObjectID || value === null || value === undefined) {
|
|
769
|
-
return null;
|
|
770
|
-
}
|
|
771
|
-
if (typeof value === "object") {
|
|
772
|
-
const text = String(value);
|
|
773
|
-
return text.trim().length > 0 && text !== "[object Object]" ? text : null;
|
|
774
|
-
}
|
|
775
|
-
return null;
|
|
104
|
+
// Null on the Community Edition (the facade also swallows a module's errors).
|
|
105
|
+
getRecorder() {
|
|
106
|
+
return EnterpriseEdition.getAuditLogRecorder();
|
|
776
107
|
}
|
|
777
108
|
}
|
|
778
109
|
export default new AuditLogService();
|